-
Notifications
You must be signed in to change notification settings - Fork 1.4k
Several allocation fixes in Scheduler.cs #11802
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
|
@@ -698,7 +698,7 @@ private void ScheduleUnassignedRequests(List<ScheduleResponse> responses) | |||||
|
||||||
// Resume any work available which has already been assigned to specific nodes. | ||||||
ResumeRequiredWork(responses); | ||||||
HashSet<int> idleNodes = new HashSet<int>(); | ||||||
HashSet<int> idleNodes = new HashSet<int>(_availableNodes.Count); | ||||||
foreach (int availableNodeId in _availableNodes.Keys) | ||||||
{ | ||||||
if (!_schedulingData.IsNodeWorking(availableNodeId)) | ||||||
|
@@ -991,7 +991,8 @@ private void AssignUnscheduledRequestsToInProcNode(List<ScheduleResponse> respon | |||||
{ | ||||||
if (idleNodes.Contains(InProcNodeId)) | ||||||
{ | ||||||
List<SchedulableRequest> unscheduledRequests = new List<SchedulableRequest>(_schedulingData.UnscheduledRequestsWhichCanBeScheduled); | ||||||
List<SchedulableRequest> unscheduledRequests = new List<SchedulableRequest>(_schedulingData.UnscheduledRequestsCount); | ||||||
unscheduledRequests.AddRange(_schedulingData.UnscheduledRequestsWhichCanBeScheduled); | ||||||
foreach (SchedulableRequest request in unscheduledRequests) | ||||||
{ | ||||||
if (CanScheduleRequestToNode(request, InProcNodeId) && shouldBeScheduled(request)) | ||||||
|
@@ -1246,7 +1247,7 @@ private void AssignUnscheduledRequestsWithMaxWaitingRequests2(List<ScheduleRespo | |||||
private void AssignUnscheduledRequestsFIFO(List<ScheduleResponse> responses, HashSet<int> idleNodes) | ||||||
{ | ||||||
// Assign requests on a first-come/first-serve basis | ||||||
foreach (int nodeId in idleNodes) | ||||||
foreach (SchedulableRequest unscheduledRequest in _schedulingData.UnscheduledRequestsWhichCanBeScheduled) | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The loop order in AssignUnscheduledRequestsFIFO has been reversed, which may affect the intended FIFO scheduling behavior. Please verify that iterating unscheduled requests first and then idle nodes produces the expected request assignment order.
Suggested change
Copilot uses AI. Check for mistakes. Positive FeedbackNegative Feedback There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What's the motivation for the switch? Document in a comment. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Enumerating over |
||||||
{ | ||||||
// Don't overload the system. | ||||||
if (AtSchedulingLimit()) | ||||||
|
@@ -1255,7 +1256,7 @@ private void AssignUnscheduledRequestsFIFO(List<ScheduleResponse> responses, Has | |||||
return; | ||||||
} | ||||||
|
||||||
foreach (SchedulableRequest unscheduledRequest in _schedulingData.UnscheduledRequestsWhichCanBeScheduled) | ||||||
foreach (int nodeId in idleNodes) | ||||||
{ | ||||||
if (CanScheduleRequestToNode(unscheduledRequest, nodeId)) | ||||||
{ | ||||||
|
@@ -1956,33 +1957,25 @@ private ScheduleResponse TrySatisfyRequestFromCache(int nodeForResults, BuildReq | |||||
/// <returns>True if caches misses are allowed, false otherwise</returns> | ||||||
private bool CheckIfCacheMissOnReferencedProjectIsAllowedAndErrorIfNot(int nodeForResults, BuildRequest request, List<ScheduleResponse> responses, out Action<ILoggingService> emitNonErrorLogs) | ||||||
{ | ||||||
emitNonErrorLogs = _ => { }; | ||||||
emitNonErrorLogs = static _ => { }; | ||||||
|
||||||
ProjectIsolationMode isolateProjects = _componentHost.BuildParameters.ProjectIsolationMode; | ||||||
var configCache = (IConfigCache)_componentHost.GetComponent(BuildComponentType.ConfigCache); | ||||||
|
||||||
// do not check root requests as nothing depends on them | ||||||
if (isolateProjects == ProjectIsolationMode.False || request.IsRootRequest || request.SkipStaticGraphIsolationConstraints | ||||||
|| SkipNonexistentTargetsIfExistentTargetsHaveResults(request)) | ||||||
|| SkipNonexistentTargetsIfExistentTargetsHaveResults(request, _configCache, _resultsCache)) | ||||||
{ | ||||||
bool logComment = ((isolateProjects == ProjectIsolationMode.True || isolateProjects == ProjectIsolationMode.MessageUponIsolationViolation) && request.SkipStaticGraphIsolationConstraints); | ||||||
if (logComment) | ||||||
{ | ||||||
// retrieving the configs is not quite free, so avoid computing them eagerly | ||||||
var configs = GetConfigurations(); | ||||||
|
||||||
emitNonErrorLogs = ls => ls.LogComment( | ||||||
NewBuildEventContext(), | ||||||
MessageImportance.Normal, | ||||||
"SkippedConstraintsOnRequest", | ||||||
configs.ParentConfig.ProjectFullPath, | ||||||
configs.RequestConfig.ProjectFullPath); | ||||||
emitNonErrorLogs = GetLoggingServiceAction(configCache, request, _schedulingData); | ||||||
} | ||||||
|
||||||
return true; | ||||||
} | ||||||
|
||||||
(BuildRequestConfiguration requestConfig, BuildRequestConfiguration parentConfig) = GetConfigurations(); | ||||||
(BuildRequestConfiguration requestConfig, BuildRequestConfiguration parentConfig) = GetConfigurations(configCache, request, _schedulingData); | ||||||
|
||||||
// allow self references (project calling the msbuild task on itself, potentially with different global properties) | ||||||
if (parentConfig.ProjectFullPath.Equals(requestConfig.ProjectFullPath, StringComparison.OrdinalIgnoreCase)) | ||||||
|
@@ -2010,7 +2003,7 @@ private bool CheckIfCacheMissOnReferencedProjectIsAllowedAndErrorIfNot(int nodeF | |||||
|
||||||
return false; | ||||||
|
||||||
BuildEventContext NewBuildEventContext() | ||||||
static BuildEventContext NewBuildEventContext(BuildRequest request) | ||||||
{ | ||||||
return new BuildEventContext( | ||||||
request.SubmissionId, | ||||||
|
@@ -2021,13 +2014,33 @@ BuildEventContext NewBuildEventContext() | |||||
BuildEventContext.InvalidTaskId); | ||||||
} | ||||||
|
||||||
(BuildRequestConfiguration RequestConfig, BuildRequestConfiguration ParentConfig) GetConfigurations() | ||||||
static (BuildRequestConfiguration RequestConfig, BuildRequestConfiguration ParentConfig) GetConfigurations(IConfigCache configCache, BuildRequest request, SchedulingData schedulingData) | ||||||
{ | ||||||
BuildRequestConfiguration buildRequestConfiguration = configCache[request.ConfigurationId]; | ||||||
|
||||||
// Need the parent request. It might be blocked or executing; check both. | ||||||
SchedulableRequest parentRequest = _schedulingData.BlockedRequests.FirstOrDefault(r => r.BuildRequest.GlobalRequestId == request.ParentGlobalRequestId) | ||||||
?? _schedulingData.ExecutingRequests.FirstOrDefault(r => r.BuildRequest.GlobalRequestId == request.ParentGlobalRequestId); | ||||||
SchedulableRequest parentRequest = null; | ||||||
|
||||||
foreach (var r in schedulingData.BlockedRequests) | ||||||
{ | ||||||
if (r.BuildRequest.GlobalRequestId == request.ParentGlobalRequestId) | ||||||
{ | ||||||
parentRequest = r; | ||||||
break; | ||||||
} | ||||||
} | ||||||
|
||||||
if (parentRequest is null) | ||||||
{ | ||||||
foreach (var r in schedulingData.ExecutingRequests) | ||||||
{ | ||||||
if (r.BuildRequest.GlobalRequestId == request.ParentGlobalRequestId) | ||||||
{ | ||||||
parentRequest = r; | ||||||
break; | ||||||
} | ||||||
} | ||||||
} | ||||||
|
||||||
ErrorUtilities.VerifyThrowInternalNull(parentRequest); | ||||||
ErrorUtilities.VerifyThrow( | ||||||
|
@@ -2038,20 +2051,20 @@ BuildEventContext NewBuildEventContext() | |||||
return (buildRequestConfiguration, parentConfiguration); | ||||||
} | ||||||
|
||||||
string ConcatenateGlobalProperties(BuildRequestConfiguration configuration) | ||||||
static string ConcatenateGlobalProperties(BuildRequestConfiguration configuration) | ||||||
{ | ||||||
return string.Join("; ", configuration.GlobalProperties.Select<ProjectPropertyInstance, string>(p => $"{p.Name}={p.EvaluatedValue}")); | ||||||
return string.Join("; ", configuration.GlobalProperties.Select<ProjectPropertyInstance, string>(static p => $"{p.Name}={p.EvaluatedValue}")); | ||||||
} | ||||||
|
||||||
bool SkipNonexistentTargetsIfExistentTargetsHaveResults(BuildRequest buildRequest) | ||||||
static bool SkipNonexistentTargetsIfExistentTargetsHaveResults(BuildRequest buildRequest, IConfigCache configCache, IResultsCache resultsCache) | ||||||
{ | ||||||
// Return early if the top-level target(s) of this build request weren't requested to be skipped if nonexistent. | ||||||
if ((buildRequest.BuildRequestDataFlags & BuildRequestDataFlags.SkipNonexistentTargets) != BuildRequestDataFlags.SkipNonexistentTargets) | ||||||
{ | ||||||
return false; | ||||||
} | ||||||
|
||||||
BuildResult requestResults = _resultsCache.GetResultsForConfiguration(buildRequest.ConfigurationId); | ||||||
BuildResult requestResults = resultsCache.GetResultsForConfiguration(buildRequest.ConfigurationId); | ||||||
|
||||||
// On a self-referenced build, cache misses are allowed. | ||||||
if (requestResults == null) | ||||||
|
@@ -2061,9 +2074,9 @@ bool SkipNonexistentTargetsIfExistentTargetsHaveResults(BuildRequest buildReques | |||||
|
||||||
// A cache miss on at least one existing target without results is disallowed, | ||||||
// as it violates isolation constraints. | ||||||
foreach (string target in request.Targets) | ||||||
foreach (string target in buildRequest.Targets) | ||||||
{ | ||||||
if (_configCache[buildRequest.ConfigurationId] | ||||||
if (configCache[buildRequest.ConfigurationId] | ||||||
.ProjectTargets | ||||||
.Contains(target) && | ||||||
!requestResults.HasResultsForTarget(target)) | ||||||
|
@@ -2076,6 +2089,21 @@ bool SkipNonexistentTargetsIfExistentTargetsHaveResults(BuildRequest buildReques | |||||
// to skip nonexistent targets. | ||||||
return true; | ||||||
} | ||||||
|
||||||
static Action<ILoggingService> GetLoggingServiceAction(IConfigCache configCache, BuildRequest request, SchedulingData schedulingData) | ||||||
{ | ||||||
// retrieving the configs is not quite free, so avoid computing them eagerly | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can you elaborate on this? Doesn't the next line realize the configs? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Oh this is copy/pasted from the original. Not sure I see the benefit of this extraction, does it cause some subtle allocation thing? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The goal here is to avoid the allocation of a closure object, and an unfortunate consequence is that if there's a capture anywhere in the body of the method, the closure object is created. This happens regardless of if the code that would use the closure is called. For example this:
Compiles to this:
By pulling the offending code into a local function, we avoid the unconditional allocation:
Instead, we only allocate when we call the local function:
Creating the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. One note, this statement There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
TIL. This makes sense then. I'd remove the comment since it's meaningless in this context--the idea was "don't create the context if you won't need it" but if this method is called you need it. |
||||||
(BuildRequestConfiguration requestConfig, BuildRequestConfiguration parentConfig) = GetConfigurations(configCache, request, schedulingData); | ||||||
|
||||||
Action<ILoggingService> emitNonErrorLogs = ls => ls.LogComment( | ||||||
NewBuildEventContext(request), | ||||||
MessageImportance.Normal, | ||||||
"SkippedConstraintsOnRequest", | ||||||
parentConfig.ProjectFullPath, | ||||||
requestConfig.ProjectFullPath); | ||||||
|
||||||
return emitNonErrorLogs; | ||||||
} | ||||||
} | ||||||
|
||||||
/// <summary> | ||||||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Using _schedulingData.UnscheduledRequestsCount as the list capacity assumes it matches the actual number of unscheduled requests. Please ensure that this value is always in sync with _schedulingData.UnscheduledRequestsWhichCanBeScheduled to avoid potential resizing during list population.
Copilot uses AI. Check for mistakes.