WIP - optimizing start times

This commit is contained in:
2026-05-30 17:45:37 -05:00
parent b744f70b0b
commit 2c176e5dd7
5 changed files with 153 additions and 4 deletions
@@ -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();
+48 -3
View File
@@ -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,
+54
View File
@@ -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.