WIP - optimizing start times
This commit is contained in:
@@ -125,6 +125,16 @@ public sealed class HardwareMonitorService : IDisposable
|
||||
RebuildTree(raiseTreeRebuilt);
|
||||
}
|
||||
|
||||
public async Task OpenAsync(bool raiseTreeRebuilt = true, CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (_isOpen)
|
||||
return;
|
||||
|
||||
await Computer.OpenAsync(cancellationToken).ConfigureAwait(false);
|
||||
_isOpen = true;
|
||||
RebuildTree(raiseTreeRebuilt);
|
||||
}
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
if (_isOpen)
|
||||
|
||||
@@ -676,7 +676,7 @@ public sealed class MainWindowViewModel : ViewModelBase, IDisposable
|
||||
try
|
||||
{
|
||||
StatusText = "Initializing hardware sensors...";
|
||||
await Task.Run(() => _hardwareMonitor.Open(raiseTreeRebuilt: false));
|
||||
await _hardwareMonitor.OpenAsync(raiseTreeRebuilt: false);
|
||||
UpdateRoot();
|
||||
ApplySensorValuesTimeWindow();
|
||||
StartWebServerFromSettings();
|
||||
|
||||
@@ -516,6 +516,39 @@ public class Computer : IComputer
|
||||
/// enabled.
|
||||
/// </summary>
|
||||
public void Open()
|
||||
{
|
||||
if (_open)
|
||||
return;
|
||||
|
||||
OpenInternal(CancellationToken.None);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Asynchronously performs the same work as <see cref="Open()" />. The required global setup (SMBIOS, mutexes and
|
||||
/// <see cref="OpCode" />) and hardware group discovery run on a background thread; discovery order and behavior
|
||||
/// match <see cref="Open()" />.
|
||||
/// </summary>
|
||||
/// <returns>A task that completes once all enabled hardware groups have been discovered.</returns>
|
||||
public Task OpenAsync()
|
||||
{
|
||||
return OpenAsync(CancellationToken.None);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Asynchronously performs the same work as <see cref="Open()" />, observing the supplied <paramref name="cancellationToken" />
|
||||
/// between initialization phases.
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken">Token used to cancel initialization before it completes.</param>
|
||||
/// <returns>A task that completes once all enabled hardware groups have been discovered.</returns>
|
||||
public Task OpenAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
if (_open)
|
||||
return Task.CompletedTask;
|
||||
|
||||
return Task.Run(() => OpenInternal(cancellationToken), cancellationToken);
|
||||
}
|
||||
|
||||
private void OpenInternal(CancellationToken cancellationToken)
|
||||
{
|
||||
if (_open)
|
||||
return;
|
||||
@@ -526,6 +559,7 @@ public class Computer : IComputer
|
||||
{
|
||||
using HardwareStartupTrace startupTrace = HardwareStartupTrace.Create(_settings);
|
||||
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
_smbios = Measure(startupTrace, "SMBios", () => new SMBios());
|
||||
|
||||
if (Software.OperatingSystem.IsWindows8OrGreater)
|
||||
@@ -533,9 +567,10 @@ public class Computer : IComputer
|
||||
else
|
||||
startupTrace?.Skip("Mutexes.Open", "Operating system is older than Windows 8.");
|
||||
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
Measure(startupTrace, "OpCode.Open", OpCode.Open);
|
||||
|
||||
AddGroups(startupTrace);
|
||||
AddGroups(startupTrace, cancellationToken);
|
||||
|
||||
_open = true;
|
||||
}
|
||||
@@ -546,17 +581,21 @@ public class Computer : IComputer
|
||||
}
|
||||
}
|
||||
|
||||
private void AddGroups(HardwareStartupTrace startupTrace)
|
||||
private void AddGroups(HardwareStartupTrace startupTrace, CancellationToken cancellationToken)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
if (_motherboardEnabled)
|
||||
AddMeasuredGroup(startupTrace, "MotherboardGroup", () => new MotherboardGroup(_smbios, _settings));
|
||||
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
if (_cpuEnabled)
|
||||
AddMeasuredGroup(startupTrace, "CpuGroup", () => new CpuGroup(_settings, startupTrace));
|
||||
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
if (_memoryEnabled)
|
||||
AddMeasuredGroup(startupTrace, "MemoryGroup", () => new MemoryGroup(_settings, startupTrace));
|
||||
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
if (_gpuEnabled)
|
||||
{
|
||||
AddMeasuredGroup(startupTrace, "AmdGpuGroup", () => new AmdGpuGroup(_settings));
|
||||
@@ -571,9 +610,11 @@ public class Computer : IComputer
|
||||
AddMeasuredGroup(startupTrace, "IntelGpuGroup", () => new IntelGpuGroup(GetIntelCpus(), _settings));
|
||||
}
|
||||
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
if (_powerMonitorEnabled)
|
||||
AddMeasuredGroup(startupTrace, "PowerMonitorGroup", () => new PowerMonitorGroup(_settings));
|
||||
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
if (_controllerEnabled)
|
||||
{
|
||||
AddMeasuredGroup(startupTrace, "TBalancerGroup", () => new TBalancerGroup(_settings));
|
||||
@@ -586,6 +627,7 @@ public class Computer : IComputer
|
||||
AddMeasuredGroup(startupTrace, "MsiGroup", () => new MsiGroup(_settings));
|
||||
}
|
||||
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
if (_storageEnabled)
|
||||
AddMeasuredGroup(startupTrace,
|
||||
"StorageGroup",
|
||||
@@ -594,6 +636,7 @@ public class Computer : IComputer
|
||||
DeferStorageDetectionSetting,
|
||||
DeferStorageDetectionEnvironmentVariable);
|
||||
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
if (_networkEnabled)
|
||||
AddMeasuredGroup(startupTrace,
|
||||
"NetworkGroup",
|
||||
@@ -602,12 +645,14 @@ public class Computer : IComputer
|
||||
DeferNetworkDetectionSetting,
|
||||
DeferNetworkDetectionEnvironmentVariable);
|
||||
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
if (_psuEnabled)
|
||||
{
|
||||
AddMeasuredGroup(startupTrace, "CorsairPsuGroup", () => new CorsairPsuGroup(_settings));
|
||||
AddMeasuredGroup(startupTrace, "MsiPsuGroup", () => new MsiPsuGroup(_settings));
|
||||
}
|
||||
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
if (_batteryEnabled)
|
||||
AddMeasuredGroup(startupTrace, "BatteryGroup", () => new BatteryGroup(_settings));
|
||||
}
|
||||
@@ -827,7 +872,7 @@ public class Computer : IComputer
|
||||
|
||||
StartDeferredGroupRun();
|
||||
RemoveGroups();
|
||||
AddGroups(null);
|
||||
AddGroups(null, CancellationToken.None);
|
||||
}
|
||||
|
||||
private void RemoveGroups()
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
Libre Hardware Monitor hardware startup timing
|
||||
Timestamp: 2026-05-30 22:41:10Z
|
||||
Total elapsed: 239.124 ms
|
||||
|
||||
Phase,ElapsedMs,HardwareCount,Status,Detail
|
||||
SMBios,3.084,,OK,
|
||||
Mutexes.Open,1.752,,OK,
|
||||
OpCode.Open,1.407,,OK,
|
||||
MotherboardGroup,36.351,1,OK,
|
||||
CpuGroup.GetProcessorThreads,9.124,,OK,"1 processor(s), 32 thread(s)"
|
||||
CpuGroup.Processor0.GroupThreadsByCore,0.508,,OK,"24 core(s), 32 thread(s)"
|
||||
GenericCpu.CpuLoad,0.700,,OK,Available
|
||||
GenericCpu.EstimateTimeStampCounterFrequency,25.262,,OK,
|
||||
IntelCpu.CreateMsrModule,0.265,,OK,
|
||||
IntelCpu.InitialUpdate,0.000,,SKIPPED,Deferred until first regular update.
|
||||
CpuGroup.Processor0.IntelCpu,37.206,,OK,"13th Gen Intel Core i9-13980HX, 139 sensor(s)"
|
||||
CpuGroup,47.798,1,OK,
|
||||
MemoryGroup.Driver.Configure,0.833,,OK,
|
||||
MemoryGroup.VirtualMemory,0.201,,OK,
|
||||
MemoryGroup.TotalMemory,0.160,,OK,
|
||||
MemoryGroup.Driver.LoadDriver,0.123,,OK,Loaded
|
||||
MemoryGroup.DimmDetection,0.000,,SKIPPED,Deferred to background.
|
||||
MemoryGroup,2.577,2,OK,
|
||||
AmdGpuGroup,1.564,0,OK,
|
||||
NvidiaGroup,0.000,,SKIPPED,Deferred to background.
|
||||
IntelGpuGroup,76.887,1,OK,
|
||||
PowerMonitorGroup,1.637,0,OK,
|
||||
TBalancerGroup,1.902,0,OK,
|
||||
HeatmasterGroup,0.820,0,OK,
|
||||
AquaComputerGroup,49.511,0,OK,
|
||||
AeroCoolGroup,1.070,0,OK,
|
||||
NzxtGroup,1.038,0,OK,
|
||||
RazerGroup,0.809,0,OK,
|
||||
ArcticGroup,0.603,0,OK,
|
||||
MsiGroup,1.227,0,OK,
|
||||
StorageGroup,0.000,,SKIPPED,Deferred to background.
|
||||
NetworkGroup,0.000,,SKIPPED,Deferred to background.
|
||||
CorsairPsuGroup,1.152,0,OK,
|
||||
MsiPsuGroup,0.953,0,OK,
|
||||
BatteryGroup,2.743,1,OK,
|
||||
@@ -0,0 +1,54 @@
|
||||
There doesn’t appear to be a test suite around Computer.Open() in the workspace, so the plan needs to add instrumentation and regression checks before changing
|
||||
scheduling. I’m going to summarize a conservative path: measure first, make UI progressively populated, then parallelize only independent groups with explicit
|
||||
resource classes.
|
||||
|
||||
• Updated Plan
|
||||
└ ✔ Trace current hardware initialization path
|
||||
✔ Identify blocking and serial discovery hotspots
|
||||
□ Present prioritized optimization plan
|
||||
|
||||
──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
|
||||
• Findings
|
||||
Computer.Open() is fully serial today: SMBIOS, mutex setup, OpCode.Open(), then every enabled group in fixed order. Expensive candidates include motherboard LPC/EC/ IPMI, memory SPD driver/SMBus probing, GPU vendor APIs, storage enumeration, network enumeration, USB/serial controller scans, PSUs, power monitors, and battery
|
||||
device IO.
|
||||
|
||||
Some groups are independent, but not all are safe to parallelize blindly. ISA/PCI/EC/USB sensor paths use shared mutexes and some constructors perform fixed sleeps
|
||||
or driver/native calls.
|
||||
|
||||
Plan
|
||||
|
||||
1. Add startup timing instrumentation first.
|
||||
Record per phase: SMBIOS, OpCode.Open, each IGroup constructor, hardware count, exceptions, and elapsed time. Put this behind a debug/log setting so we can
|
||||
compare before/after without guessing.
|
||||
|
||||
2. Improve perceived startup in WinUI.
|
||||
Show the shell immediately, then populate groups as they complete. Status should say which group is initializing. This preserves correctness and makes slow
|
||||
probes visible instead of looking hung.
|
||||
|
||||
3. Split Computer.Open() into staged group discovery.
|
||||
Keep required global setup serial:
|
||||
SMBios, Mutexes.Open, OpCode.Open.
|
||||
Then schedule group creation through a small initialization coordinator.
|
||||
|
||||
4. Parallelize only low-conflict groups first.
|
||||
Good first candidates:
|
||||
CpuGroup, NetworkGroup, BatteryGroup, StorageGroup, AMD/NVIDIA GPU groups, non-dependent PSU groups.
|
||||
5. Add dependency/resource classes.
|
||||
Use explicit buckets such as Cpu, GpuVendorApi, Storage, Network, Battery, IsaBus, PciBus, Ec, UsbHid, SerialUsb.
|
||||
Groups in the same risky bucket stay serialized; independent buckets can run concurrently.
|
||||
Controller groups contain some worst-case sleeps and USB/serial scans. Load them after core CPU/GPU/motherboard/storage sensors, or expose “defer fan/controller
|
||||
detection” while still defaulting to correctness.
|
||||
HardwareAdded should fire as each group completes. Computer.Hardware needs lock-safe snapshots so UI and web server can read while discovery continues.
|
||||
|
||||
6. Regression checks.
|
||||
Compare before/after reports and sensor identifiers on the same machine. The correctness bar should be: same enabled hardware groups eventually discovered, same
|
||||
sensor identifiers where hardware is present, no lost late-added memory/GPU devices.
|
||||
|
||||
Recommended implementation order:
|
||||
|
||||
1. Timing instrumentation.
|
||||
2. Progressive WinUI population/status.
|
||||
3. Async Computer.OpenAsync with serial behavior.
|
||||
4. Parallelize low-conflict groups.
|
||||
5. Defer/lazy heavy controller probes.
|
||||
6. Broaden with resource-bucket scheduling after logs prove hotspots.
|
||||
Reference in New Issue
Block a user