Refactor and optimize register allocator and live set computations - #1288
Conversation
Renamed KillSite to KillSites for clarity, replaced linear search with binary search in FindKillAllSite for better performance, and updated GetVirtualRegisters to skip physical registers. Improved GetRegisterMoves by indexing intervals by start slot and replaced break with continue for more accurate control flow. These changes enhance efficiency and maintainability of the register allocator.
Refactored ComputeGlobalLiveSets to reuse BitArray instances for liveOut and liveIn, resetting and updating them in place instead of allocating new arrays per block. Improved change detection logic to update only when necessary, reducing memory usage and improving efficiency.
Refactored use/def position lookups in LiveRange to use binary search methods, improving performance for large lists. Added GetPreviousUse and GetPreviousDef methods. Helper methods are implemented as private static functions.
Refactored DelayedIntervalTree to delay add/remove operations and short-circuit queries, reducing tree manipulations. Added extensive unit tests for edge cases and pending operation logic. IntervalTree now uses loops instead of recursion for search/insertion and MaxEnd updates, improving performance and stack usage. Added overloads for search methods to reduce allocations and new TrySearchFirstOverlapping methods. Improved documentation and ensured no unsafe or auto-generated code was modified.
There was a problem hiding this comment.
Pull request overview
This PR refactors and optimizes several hot paths in the register allocator, primarily around interval tree querying, live-set computation, and live-range position lookups, with additional unit tests added for the delayed interval tree wrapper.
Changes:
- Added allocation-free interval tree search overloads and
TrySearchFirstOverlappingAPIs; reduced recursion and improvedMaxEndmaintenance logic. - Optimized register allocator live-set computation, kill-site lookup, and move-resolution interval lookups.
- Expanded xUnit coverage for
DelayedIntervalTreeand adjusted scheduler queue status logging output.
Reviewed changes
Copilot reviewed 6 out of 6 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
| Source/Mosa.Compiler.Framework/RegisterAllocator/RedBlackTree/IntervalTree.cs | Adds allocation-free search overloads, TrySearchFirstOverlapping, iterative subtree search, and MaxEnd update refactors. |
| Source/Mosa.Compiler.Framework/RegisterAllocator/RedBlackTree/DelayedIntervalTree.cs | Adds short-circuit logic for pending ops, TrySearchFirstOverlapping, allocation-free search overloads, and a ToString() override. |
| Source/Mosa.Compiler.Framework/RegisterAllocator/LiveRange.cs | Replaces linear scans with binary searches for next/previous def/use lookups and adds helper search routines. |
| Source/Mosa.Compiler.Framework/RegisterAllocator/BaseRegisterAllocator.cs | Reuses BitArray buffers for global live sets, renames/optimizes kill-site handling, improves virtual-register enumeration, and accelerates move lookup via indexing by start. |
| Source/Mosa.Compiler.Framework/MethodScheduler.cs | Simplifies queue status CPU logging output. |
| Source/Mosa.Compiler.Framework.xUnit/DelayedIntervalTreeTests.cs | Adds extensive tests covering pending add/delete behavior, searching, replace behavior, enumeration flushing, and caller-provided result lists. |
Comments suppressed due to low confidence (1)
Source/Mosa.Compiler.Framework/RegisterAllocator/RedBlackTree/DelayedIntervalTree.cs:111
- In
Remove(...), cancellation of a pending add uses endpoint containment checks. If the removal range fully contains the pending add interval, neither endpoint may be inside the pending interval and the pending add won’t be cancelled, even thoughRemovewill later remove the overlapping interval in the underlyingIntervalTree. Consider usingOverlaps(...)(or explicit equality ifRemoveis intended to only target exact intervals).
// If pending add overlaps, cancel it
if (delayedAdd && (Contains(delayedAddStart, delayedAddEnd, start) || Contains(delayedAddStart, delayedAddEnd, end)))
{
delayedAdd = false;
return;
}
…DelayedIntervalTree.cs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Signed-off-by: Phil Garcia <phil@thinkedge.com>
…DelayedIntervalTree.cs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Signed-off-by: Phil Garcia <phil@thinkedge.com>
Replaces reflection-based platform registration with explicit, centralized registration via PlatformRegistrations.Register(). Adds new Mosa.Compiler.Platforms project and updates all relevant .csproj and solution files to reference it. Updates PlatformRegistry to remove assembly scanning. Includes minor code cleanups and adds x64 build configs to Mosa.Linux.sln.
… into 604-allocator
- Remove unused equivalent CPU core calculation in ReportQueueStatus. - Use Overlaps method for correct interval overlap detection in DelayedIntervalTree.Add. - Fix off-by-one errors in IntervalTree by changing MaxEnd > to MaxEnd >= in search logic.
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Signed-off-by: Phil Garcia <phil@thinkedge.com>
…sproj Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Signed-off-by: Phil Garcia <phil@thinkedge.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Signed-off-by: Phil Garcia <phil@thinkedge.com>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 26 out of 28 changed files in this pull request and generated 1 comment.
Comments suppressed due to low confidence (3)
Source/Mosa.Compiler.Framework/RegisterAllocator/RedBlackTree/IntervalTree.cs:520
- IntervalTree.Replace() writes to the node returned by FindInterval without checking for Sentinel. If the interval isn’t present, FindInterval returns Sentinel and this assignment mutates the Sentinel’s Value, which can break future searches/operations. Replace should either no-op or throw when the interval doesn’t exist (and avoid writing to Sentinel).
public void Replace(int start, int end, T value)
{
var node = FindInterval(Root, new Interval(start, end));
node.Value = value;
}
Source/Mosa.Compiler.Framework/MethodScheduler.cs:399
- CalculateCpuUsage(): removing the try/catch means any exception from Process.Refresh()/TotalProcessorTime (platform-specific failures, disposed process, permission issues, etc.) will now propagate out of diagnostic reporting and can crash compilation. Please reintroduce exception handling (or otherwise harden this path) so diagnostics can’t take down the compiler.
private double CalculateCpuUsage(long currentTicks)
{
currentProcess.Refresh();
var currentCpuTime = currentProcess.TotalProcessorTime;
var cpuTimeDelta = (currentCpuTime - lastCpuTime).TotalMilliseconds;
var ticksDelta = currentTicks - lastCpuCheckTicks;
var wallTimeDelta = (ticksDelta / (double)Stopwatch.Frequency) * 1000.0; // Convert to milliseconds
lastCpuTime = currentCpuTime;
lastCpuCheckTicks = currentTicks;
if (wallTimeDelta > 0 && wallTimeDelta < 60000) // Sanity check: < 60 seconds
{
// CPU percentage divided by cores to match Task Manager (0-100% scale)
var cpuPercent = (cpuTimeDelta / wallTimeDelta / processorCount) * 100.0;
return Math.Clamp(cpuPercent, 0.0, 100.0);
}
return 0.0;
}
Source/Mosa.Compiler.Framework/RegisterAllocator/RedBlackTree/DelayedIntervalTree.cs:111
- Remove(): the pending-add cancellation check only tests whether the endpoints of the remove range fall inside the pending add interval. If the remove range fully covers the pending add (e.g., pending [10,20], remove [5,25]), the pending add will not be cancelled and the tree can report incorrect results. Use a true overlap test (like the Overlaps helper) for this condition.
public void Remove(int start, int end)
{
// If pending add overlaps, cancel it
if (delayedAdd && (Contains(delayedAddStart, delayedAddEnd, start) || Contains(delayedAddStart, delayedAddEnd, end)))
{
delayedAdd = false;
return;
}
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Signed-off-by: Phil Garcia <phil@thinkedge.com>
Refactored Program.cs to use static MosaSettings and Stopwatch instances, centralizing settings logic into SetRequiredSettings and UpdateSettings methods. The compile process now benchmarks multiple methods in Mosa.UnitTests.Fuzzy.Fuzz0009 and outputs timing for each via a new OutputStatus method with elapsed time prefixes. Increased default compile iterations from 10 to 100. Fuzz0009.cs changes are whitespace-only (CR removal, reformatting) with no logic modifications.
… into 604-allocator
… into 604-allocator Enable multithreaded compilation for fuzzy test methods Switch to multithreaded compilation by setting MosaSettings.Multithreading and MaxThreads, and enable method scanning and diagnostics. Replace hardcoded method compilation with dynamic collection and scheduling of up to 2000 fuzzy test methods. Add GetFuzzyMethods helper to gather relevant methods, and improve status output to reflect method count and thread usage.
Refactored Program.cs to separate single and multithreaded performance tests, reduced fuzz method count, and improved compiler hook event handling. Updated status messages and moved platform registration to its own method. Revised Azure Tools Guidelines in copilot-instructions.md for clarity. Set MethodScanner to false.
… into 604-allocator
Refactored the foreach loop in the CompileCompleted method to use explicit braces, improving readability. No functional changes were introduced.
Changed AddTraceEvent to handle CompilerEvent.Exception with an else-if instead of a separate if. This ensures the Exception and Error cases are mutually exclusive and prevents both log blocks from executing for the same event.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 31 out of 34 changed files in this pull request and generated 4 comments.
Comments suppressed due to low confidence (1)
Source/Mosa.Compiler.Framework/RegisterAllocator/RedBlackTree/DelayedIntervalTree.cs:111
Remove(start, end)is intended to cancel a pending add when the remove range overlaps it, but the current check only tests whether the remove start/end points fall inside the pending add. This misses cases like pending add [5,10] followed by remove [0,20], leavingdelayedAddactive and causing subsequentContains/Searchcalls to return results that should have been removed. Consider using the existingOverlaps(...)helper (or an equivalent overlap check) here as well.
public void Remove(int start, int end)
{
// If pending add overlaps, cancel it
if (delayedAdd && (Contains(delayedAddStart, delayedAddEnd, start) || Contains(delayedAddStart, delayedAddEnd, end)))
{
delayedAdd = false;
return;
}
Corrected a typo in the compile time measurement output message, changing "Interations" to "Iterations" for clarity.
Moved GetStandardNotifyEventStatus and IsStandardFilteredNotifyEvent into a new #region Standardization within CompilerHooks for better code organization. No functional changes were made.
Refactored MethodScanner to extract entry point and required type scheduling into separate methods for clarity. Added overloads to MethodScheduler and MosaCompiler to support batch scheduling of methods, improving performance in multithreaded fuzz tests. Changed MethodScanner default to enabled in settings. Cleaned up initialization logic for better maintainability.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 34 out of 37 changed files in this pull request and generated 6 comments.
Comments suppressed due to low confidence (1)
Source/Mosa.Compiler.Framework/RegisterAllocator/RedBlackTree/DelayedIntervalTree.cs:111
Remove(int start, int end)says it cancels a pending add when it overlaps, but the condition only checks whether the remove range endpoints fall inside the pending add. This misses overlap cases where the pending add is strictly contained within the remove range. Use the same overlap predicate used elsewhere (e.g.,Overlaps(delayedAddStart, delayedAddEnd, start, end)) so pending adds are reliably canceled for any overlap.
public void Remove(int start, int end)
{
// If pending add overlaps, cancel it
if (delayedAdd && (Contains(delayedAddStart, delayedAddEnd, start) || Contains(delayedAddStart, delayedAddEnd, end)))
{
delayedAdd = false;
return;
}
| private void ScheduledEntryPoint() | ||
| { | ||
| var entryPoint = TypeSystem.EntryPoint; | ||
|
|
||
| if (entryPoint != null) | ||
| { | ||
| MarkMethodInvoked(entryPoint); | ||
| ScheduleMethod(entryPoint); | ||
| } | ||
| } |
There was a problem hiding this comment.
ScheduledEntryPoint() name reads like a past-tense state but it performs an action (scheduling). Renaming to ScheduleEntryPoint() would better match intent and the naming of ScheduleRequiredMethods() below.
| private double CalculateCpuUsage(long currentTicks) | ||
| { | ||
| try | ||
| { | ||
| currentProcess.Refresh(); | ||
| var currentCpuTime = currentProcess.TotalProcessorTime; | ||
| var cpuTimeDelta = (currentCpuTime - lastCpuTime).TotalMilliseconds; | ||
| currentProcess.Refresh(); | ||
| var currentCpuTime = currentProcess.TotalProcessorTime; | ||
| var cpuTimeDelta = (currentCpuTime - lastCpuTime).TotalMilliseconds; | ||
|
|
||
| var ticksDelta = currentTicks - lastCpuCheckTicks; | ||
| var wallTimeDelta = (ticksDelta / (double)Stopwatch.Frequency) * 1000.0; // Convert to milliseconds | ||
| var ticksDelta = currentTicks - lastCpuCheckTicks; | ||
| var wallTimeDelta = (ticksDelta / (double)Stopwatch.Frequency) * 1000.0; // Convert to milliseconds | ||
|
|
||
| lastCpuTime = currentCpuTime; | ||
| lastCpuCheckTicks = currentTicks; | ||
| lastCpuTime = currentCpuTime; | ||
| lastCpuCheckTicks = currentTicks; | ||
|
|
||
| if (wallTimeDelta > 0 && wallTimeDelta < 60000) // Sanity check: < 60 seconds | ||
| { | ||
| // CPU percentage divided by cores to match Task Manager (0-100% scale) | ||
| var cpuPercent = (cpuTimeDelta / wallTimeDelta / processorCount) * 100.0; | ||
| return Math.Clamp(cpuPercent, 0.0, 100.0); | ||
| } | ||
| } | ||
| catch | ||
| if (wallTimeDelta > 0 && wallTimeDelta < 60000) // Sanity check: < 60 seconds | ||
| { | ||
| // Ignore any errors in CPU calculation | ||
| // CPU percentage divided by cores to match Task Manager (0-100% scale) | ||
| var cpuPercent = (cpuTimeDelta / wallTimeDelta / processorCount) * 100.0; | ||
| return Math.Clamp(cpuPercent, 0.0, 100.0); | ||
| } |
There was a problem hiding this comment.
CalculateCpuUsage is invoked from diagnostic status reporting; removing the previous try/catch means failures in Process.Refresh() / TotalProcessorTime (platform/permission/process-lifetime edge cases) can now crash compilation just for diagnostics. Please restore exception shielding (and return 0.0 on failure) so queue reporting can never take down the compiler.
| /// <summary> | ||
| /// Search interval tree for a given point, filling a caller-provided list to avoid allocation | ||
| /// </summary> | ||
| public void Search(int at, List<T> result) | ||
| { | ||
| SearchSubtree(Root, at, result); | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Search interval tree for intervals overlapping with given | ||
| /// </summary> | ||
| /// <param name="interval"></param> | ||
| /// <returns></returns> | ||
| public List<T> Search(int start, int end) | ||
| { | ||
| var result = new List<T>(); | ||
| SearchSubtree(Root, new Interval(start, end), result); | ||
| return result; | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Search interval tree for intervals overlapping with given, filling a caller-provided list to avoid allocation | ||
| /// </summary> | ||
| public void Search(int start, int end, List<T> result) | ||
| { | ||
| SearchSubtree(Root, new Interval(start, end), result); | ||
| } |
There was a problem hiding this comment.
The new allocation-free Search(..., List<T> result) overloads append to the provided list without clearing it. That is easy to misuse when callers reuse a list across queries (results accumulate silently). Consider clearing result at the start of these methods (or explicitly documenting that the list is appended-to, not overwritten, and renaming accordingly).
| /// <summary> | ||
| /// Fills a caller-provided list to avoid per-call allocation. | ||
| /// </summary> | ||
| public void Search(int at, List<T> result) | ||
| { | ||
| FlushDelete(); | ||
| tree.Search(at, result); | ||
|
|
||
| if (delayedAdd && Contains(delayedAddStart, delayedAddEnd, at)) | ||
| result.Add(delayedAddValue); | ||
| } | ||
|
|
||
| public List<T> Search(int start, int end) | ||
| { | ||
| FlushDelete(); | ||
| FlushAdd(); | ||
| return tree.Search(start, end); | ||
| var result = tree.Search(start, end); | ||
|
|
||
| // Append the pending add to the result if it overlaps [start, end], avoiding a tree insertion. | ||
| if (delayedAdd && Overlaps(delayedAddStart, delayedAddEnd, start, end)) | ||
| result.Add(delayedAddValue); | ||
|
|
||
| return result; | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Fills a caller-provided list to avoid per-call allocation. | ||
| /// </summary> | ||
| public void Search(int start, int end, List<T> result) | ||
| { | ||
| FlushDelete(); | ||
| tree.Search(start, end, result); | ||
|
|
||
| if (delayedAdd && Overlaps(delayedAddStart, delayedAddEnd, start, end)) | ||
| result.Add(delayedAddValue); | ||
| } |
There was a problem hiding this comment.
Similar to IntervalTree, the allocation-free Search(..., List<T> result) overloads here append into the caller-provided list without clearing it first, which can easily lead to accumulated/stale results when the list is reused. Consider clearing result at method entry (or documenting/renaming to make append semantics explicit).
| public override string ToString() | ||
| { | ||
| FlushDelete(); | ||
| FlushAdd(); | ||
|
|
||
| var enumerator = tree.GetEnumerator(); | ||
| if (!enumerator.MoveNext()) | ||
| return string.Empty; | ||
| return tree.ToString(); | ||
| } |
There was a problem hiding this comment.
ToString() creates an enumerator to check emptiness but never disposes it. If the underlying enumerator ever holds resources, this could leak; even if it doesn’t today, it’s better to avoid the pattern. Consider using using var enumerator = tree.GetEnumerator(); or checking emptiness via a dedicated property/Count if available.
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Signed-off-by: Phil Garcia <phil@thinkedge.com>
… into 604-allocator
Removed a large set of `[MosaUnitTest]` methods (FuzzMethod5068–5099) from Fuzz0050.cs, significantly reducing test coverage for integer/control flow fuzzing. Also added several using directives to MethodScanner.cs; no functional changes in that file.
Cleaned up MethodScanner.cs by removing unnecessary using directives: System.Drawing, System.Reflection, System.Runtime.ConstrainedExecution, and static System.Runtime.InteropServices.JavaScript.JSType. No functional changes were made.
Removed maxWorkers from the "Active" field in the queue status diagnostic message in MethodScheduler.cs. The report now shows only the number of active workers and utilization percentage, simplifying the output.
Insert safepoints by creating new blocks between backedges and loop headers, containing a SafePoint and jump. Track (backedge, header) pairs to avoid duplicates. Move insertion logic to a helper method for clarity and maintainability.
This reverts commit 87aad42.
Simplified safepoint insertion by inlining logic and removing the creation of new blocks and branch target replacement. Now uses a HashSet<BasicBlock> to track visited backedges and inserts IR.SafePoint directly after the branch. Updated logging to match the new approach.
Uncommented the ScheduleUnitTests() call in the Initialize() method, ensuring that unit test methods are now scheduled during initialization.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 35 out of 48 changed files in this pull request and generated 6 comments.
Comments suppressed due to low confidence (6)
Source/Mosa.Workspace.Experiment.Debug/Program.cs:1
MeasureCompileTimeaccepts aStopwatch stopwatchparameter but measures using the staticStopwatchfield instead, which makes the parameter ineffective and can produce incorrect timings if a different stopwatch is passed. Use thestopwatchparameter consistently inside the method (or remove the parameter and always use the static field).
Source/Mosa.Workspace.Experiment.Debug/Program.cs:1MeasureCompileTimeaccepts aStopwatch stopwatchparameter but measures using the staticStopwatchfield instead, which makes the parameter ineffective and can produce incorrect timings if a different stopwatch is passed. Use thestopwatchparameter consistently inside the method (or remove the parameter and always use the static field).
Source/Mosa.Workspace.Experiment.Debug/Program.cs:1- These settings are assigned multiple times with conflicting values within
SetRequiredSettings(), which makes the effective configuration hard to reason about. Consolidate these assignments so each setting is assigned once (or add a short comment explaining why they must be toggled).
Source/Mosa.Workspace.Experiment.Debug/Program.cs:1 - These settings are assigned multiple times with conflicting values within
SetRequiredSettings(), which makes the effective configuration hard to reason about. Consolidate these assignments so each setting is assigned once (or add a short comment explaining why they must be toggled).
Source/Mosa.Compiler.Framework/RegisterAllocator/RedBlackTree/DelayedIntervalTree.cs:111 - The “pending add overlaps” check only tests whether
startorendis contained within the pending add, which misses overlap cases like removing a range that fully covers the pending add (e.g., pending add [10,20], remove [0,100]). This contradicts the comment and the newerOverlaps(...)logic used elsewhere; useOverlaps(delayedAddStart, delayedAddEnd, start, end)here for correctness.
public void Remove(int start, int end)
{
// If pending add overlaps, cancel it
if (delayedAdd && (Contains(delayedAddStart, delayedAddEnd, start) || Contains(delayedAddStart, delayedAddEnd, end)))
{
delayedAdd = false;
return;
}
Source/Mosa.Utility.Configuration/MOSASettings.cs:1
SetDefaultSettings()no longer assigns a default value toMethodScanner. IfMosaSettingsinstances can be reused across runs, this can leaveMethodScannerin a stale state from a prior configuration. Reintroduce an explicit default assignment here (or ensure the property is always reset elsewhere before use).
| public static void Register() | ||
| { | ||
| Registry = new Dictionary<string, BaseArchitecture>(); | ||
|
|
||
| foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies()) | ||
| { | ||
| foreach (var type in assembly.GetTypes()) | ||
| { | ||
| if (!type.IsAbstract && typeof(BaseArchitecture).IsAssignableFrom(type)) | ||
| { | ||
| var platform = (BaseArchitecture)Activator.CreateInstance(type); | ||
| Add(platform); | ||
| } | ||
| } | ||
| } | ||
| } |
There was a problem hiding this comment.
PlatformRegistry.Register() previously performed reflection-based discovery and population, but now it only resets the dictionary; this is a breaking semantic change for any consumers relying on auto-discovery. Consider either (a) restoring discovery under a new opt-in method (e.g., RegisterFromLoadedAssemblies()), or (b) renaming this method to reflect its new behavior (e.g., Reset()), and updating call sites accordingly.
| } | ||
|
|
||
| private T SearchFirstOverlapping(Interval interval) | ||
| private bool TrySearchFirstOverlapping(Interval interval, out T value) |
There was a problem hiding this comment.
value = null; will not compile (or will violate nullability expectations) if T is not constrained to a nullable reference type. Prefer value = default; (and, if needed, use nullability annotations) or explicitly constrain IntervalTree<T> with where T : class if null is intended as the “not found” sentinel.
| if (node == Sentinel) | ||
| { | ||
| throw new KeyNotFoundException("No overlapping interval found."); | ||
| value = null; | ||
| return false; | ||
| } |
There was a problem hiding this comment.
value = null; will not compile (or will violate nullability expectations) if T is not constrained to a nullable reference type. Prefer value = default; (and, if needed, use nullability annotations) or explicitly constrain IntervalTree<T> with where T : class if null is intended as the “not found” sentinel.
| /// <summary> | ||
| /// Search interval tree for a given point, filling a caller-provided list to avoid allocation | ||
| /// </summary> | ||
| public void Search(int at, List<T> result) | ||
| { | ||
| SearchSubtree(Root, at, result); | ||
| } |
There was a problem hiding this comment.
The XML doc says “filling” a caller-provided list, but the implementation appends without clearing. This is easy to misuse when the same list is reused across calls. Either clear the list inside the method, or update the documentation to explicitly state that results are appended and the caller must clear the list if desired.
| private double CalculateCpuUsage(long currentTicks) | ||
| { | ||
| try | ||
| { | ||
| currentProcess.Refresh(); | ||
| var currentCpuTime = currentProcess.TotalProcessorTime; | ||
| var cpuTimeDelta = (currentCpuTime - lastCpuTime).TotalMilliseconds; | ||
| currentProcess.Refresh(); | ||
| var currentCpuTime = currentProcess.TotalProcessorTime; | ||
| var cpuTimeDelta = (currentCpuTime - lastCpuTime).TotalMilliseconds; | ||
|
|
||
| var ticksDelta = currentTicks - lastCpuCheckTicks; | ||
| var wallTimeDelta = (ticksDelta / (double)Stopwatch.Frequency) * 1000.0; // Convert to milliseconds | ||
| var ticksDelta = currentTicks - lastCpuCheckTicks; | ||
| var wallTimeDelta = (ticksDelta / (double)Stopwatch.Frequency) * 1000.0; // Convert to milliseconds | ||
|
|
||
| lastCpuTime = currentCpuTime; | ||
| lastCpuCheckTicks = currentTicks; | ||
| lastCpuTime = currentCpuTime; | ||
| lastCpuCheckTicks = currentTicks; | ||
|
|
||
| if (wallTimeDelta > 0 && wallTimeDelta < 60000) // Sanity check: < 60 seconds | ||
| { | ||
| // CPU percentage divided by cores to match Task Manager (0-100% scale) | ||
| var cpuPercent = (cpuTimeDelta / wallTimeDelta / processorCount) * 100.0; | ||
| return Math.Clamp(cpuPercent, 0.0, 100.0); | ||
| } | ||
| } | ||
| catch | ||
| if (wallTimeDelta > 0 && wallTimeDelta < 60000) // Sanity check: < 60 seconds | ||
| { | ||
| // Ignore any errors in CPU calculation | ||
| // CPU percentage divided by cores to match Task Manager (0-100% scale) | ||
| var cpuPercent = (cpuTimeDelta / wallTimeDelta / processorCount) * 100.0; | ||
| return Math.Clamp(cpuPercent, 0.0, 100.0); | ||
| } | ||
|
|
||
| return 0.0; |
There was a problem hiding this comment.
The previous implementation guarded CPU usage calculation with a try/catch, but that protection is removed. If Process.Refresh()/TotalProcessorTime throws (platform/permission/process-state issues), it can now propagate and disrupt scheduling/diagnostics. Consider restoring exception handling around this diagnostic-only code path (ideally narrowly scoped to expected exception types).
| public void Schedule(List<MosaMethod> methods) | ||
| { | ||
| Setup(); | ||
| Compiler.MethodScheduler.Schedule(methods); | ||
| } |
There was a problem hiding this comment.
This overload calls Setup() while Schedule(MosaMethod method) does not, creating inconsistent behavior and potential redundant work (especially since Compile() also calls Setup()). Align the overloads by either requiring callers to call Setup() explicitly for both, or making both overloads internally ensure setup in the same way (and ensuring Setup() is guaranteed idempotent).
No description provided.