/// <summary> /// Constructor /// </summary> public HybridExecutor() { this.LocalExecutor = new ParallelExecutor(); this.RemoteExecutor = new XGE(); XmlConfig.ApplyTo(this); if (MaxLocalActions == 0) { MaxLocalActions = Utils.GetPhysicalProcessorCount(); } }
/// <summary> /// Determines the maximum number of actions to execute in parallel, taking into account the resources available on this machine. /// </summary> /// <returns>Max number of actions to execute in parallel</returns> public virtual int GetMaxActionsToExecuteInParallel() { // Get the number of logical processors int NumLogicalCores = Utils.GetLogicalProcessorCount(); // Use WMI to figure out physical cores, excluding hyper threading. int NumPhysicalCores = Utils.GetPhysicalProcessorCount(); if (NumPhysicalCores == -1) { NumPhysicalCores = NumLogicalCores; } // The number of actions to execute in parallel is trying to keep the CPU busy enough in presence of I/O stalls. int MaxActionsToExecuteInParallel = 0; if (NumPhysicalCores < NumLogicalCores && ProcessorCountMultiplier != 1.0) { // The CPU has more logical cores than physical ones, aka uses hyper-threading. // Use multiplier if provided MaxActionsToExecuteInParallel = (int)(NumPhysicalCores * ProcessorCountMultiplier); } else if (NumPhysicalCores < NumLogicalCores && NumPhysicalCores > 4) { // The CPU has more logical cores than physical ones, aka uses hyper-threading. // Use average of logical and physical if we have "lots of cores" MaxActionsToExecuteInParallel = Math.Max((int)(NumPhysicalCores + NumLogicalCores) / 2, NumLogicalCores - 4); } // No hyper-threading. Only kicking off a task per CPU to keep machine responsive. else { MaxActionsToExecuteInParallel = NumPhysicalCores; } #if !NET_CORE if (Utils.IsRunningOnMono) { long PhysicalRAMAvailableMB = (new PerformanceCounter("Mono Memory", "Total Physical Memory").RawValue) / (1024 * 1024); // heuristic: give each action at least 1.5GB of RAM (some clang instances will need more) if the total RAM is low, or 1GB on 16+GB machines long MinMemoryPerActionMB = (PhysicalRAMAvailableMB < 16384) ? 3 * 1024 / 2 : 1024; int MaxActionsAffordedByMemory = (int)(Math.Max(1, (PhysicalRAMAvailableMB) / MinMemoryPerActionMB)); MaxActionsToExecuteInParallel = Math.Min(MaxActionsToExecuteInParallel, MaxActionsAffordedByMemory); } #endif MaxActionsToExecuteInParallel = Math.Max(1, Math.Min(MaxActionsToExecuteInParallel, MaxProcessorCount)); return(MaxActionsToExecuteInParallel); }
public override bool ExecuteActions(List <Action> Actions, bool bLogDetailedActionStats) { bool SNDBSResult = true; if (Actions.Count > 0) { // Generate any needed templates. Can only generate one per executable, so just use the first Action for reference IEnumerable <Action> CommandPaths = Actions.GroupBy(a => a.CommandPath).Select(g => g.FirstOrDefault()).ToList(); foreach (Action CommandPath in CommandPaths) { PrepareToolTemplate(CommandPath); } // Generate include-rewrite-rules.ini. GenerateSNDBSIncludeRewriteRules(); // Use WMI to figure out physical cores, excluding hyper threading. int NumCores = Utils.GetPhysicalProcessorCount(); // If we failed to detect the number of cores, default to the logical processor count if (NumCores == -1) { NumCores = System.Environment.ProcessorCount; } // The number of actions to execute in parallel is trying to keep the CPU busy enough in presence of I/O stalls. MaxActionsToExecuteInParallel = 0; // The CPU has more logical cores than physical ones, aka uses hyper-threading. if (NumCores < System.Environment.ProcessorCount) { MaxActionsToExecuteInParallel = (int)(NumCores * ProcessorCountMultiplier); } // No hyper-threading. Only kicking off a task per CPU to keep machine responsive. else { MaxActionsToExecuteInParallel = NumCores; } MaxActionsToExecuteInParallel = Math.Min(MaxActionsToExecuteInParallel, MaxProcessorCount); JobNumber = 1; Dictionary <Action, ActionThread> ActionThreadDictionary = new Dictionary <Action, ActionThread>(); while (true) { bool bUnexecutedActions = false; foreach (Action Action in Actions) { ActionThread ActionThread = null; bool bFoundActionProcess = ActionThreadDictionary.TryGetValue(Action, out ActionThread); if (bFoundActionProcess == false) { bUnexecutedActions = true; if (!ExecuteActions(Actions, ActionThreadDictionary)) { return(false); } break; } } if (bUnexecutedActions == false) { break; } } Log.WriteLineIf(bLogDetailedActionStats, LogEventType.Console, "-------- Begin Detailed Action Stats ----------------------------------------------------------"); Log.WriteLineIf(bLogDetailedActionStats, LogEventType.Console, "^Action Type^Duration (seconds)^Tool^Task^Using PCH"); double TotalThreadSeconds = 0; // Check whether any of the tasks failed and log action stats if wanted. foreach (KeyValuePair <Action, ActionThread> ActionProcess in ActionThreadDictionary) { Action Action = ActionProcess.Key; ActionThread ActionThread = ActionProcess.Value; // Check for pending actions, preemptive failure if (ActionThread == null) { SNDBSResult = false; continue; } // Check for executed action but general failure if (ActionThread.ExitCode != 0) { SNDBSResult = false; } // Log CPU time, tool and task. double ThreadSeconds = Action.Duration.TotalSeconds; Log.WriteLineIf(bLogDetailedActionStats, LogEventType.Console, "^{0}^{1:0.00}^{2}^{3}", Action.ActionType.ToString(), ThreadSeconds, Action.CommandPath.GetFileName(), Action.StatusDescription); // Keep track of total thread seconds spent on tasks. TotalThreadSeconds += ThreadSeconds; } Log.TraceInformation("-------- End Detailed Actions Stats -----------------------------------------------------------"); // Log total CPU seconds and numbers of processors involved in tasks. Log.WriteLineIf(bLogDetailedActionStats, LogEventType.Console, "Cumulative thread seconds ({0} processors): {1:0.00}", System.Environment.ProcessorCount, TotalThreadSeconds); } // Delete the include-rewrite-rules.ini if it was generated. if (File.Exists(IncludeRewriteRulesFile.FullName)) { File.Delete(IncludeRewriteRulesFile.FullName); } return(SNDBSResult); }
/// <summary> /// Executes the specified actions locally. /// </summary> /// <returns>True if all the tasks successfully executed, or false if any of them failed.</returns> public override bool ExecuteActions(List <Action> Actions, bool bLogDetailedActionStats) { // Time to sleep after each iteration of the loop in order to not busy wait. const float LoopSleepTime = 0.1f; // Use WMI to figure out physical cores, excluding hyper threading. int NumCores = Utils.GetPhysicalProcessorCount(); if (NumCores == -1) { NumCores = System.Environment.ProcessorCount; } // The number of actions to execute in parallel is trying to keep the CPU busy enough in presence of I/O stalls. int MaxActionsToExecuteInParallel = 0; if (NumCores < System.Environment.ProcessorCount && ProcessorCountMultiplier != 1.0) { // The CPU has more logical cores than physical ones, aka uses hyper-threading. // Use multiplier if provided MaxActionsToExecuteInParallel = (int)(NumCores * ProcessorCountMultiplier); } else if (NumCores < System.Environment.ProcessorCount && NumCores > 4) { // The CPU has more logical cores than physical ones, aka uses hyper-threading. // Use average of logical and physical if we have "lots of cores" MaxActionsToExecuteInParallel = (int)(NumCores + System.Environment.ProcessorCount) / 2; } // No hyper-threading. Only kicking off a task per CPU to keep machine responsive. else { MaxActionsToExecuteInParallel = NumCores; } if (Utils.IsRunningOnMono) { // heuristic: give each action at least 1.5GB of RAM (some clang instances will need more, actually) long MinMemoryPerActionMB = 3 * 1024 / 2; long PhysicalRAMAvailableMB = (new PerformanceCounter("Mono Memory", "Total Physical Memory").RawValue) / (1024 * 1024); int MaxActionsAffordedByMemory = (int)(Math.Max(1, (PhysicalRAMAvailableMB) / MinMemoryPerActionMB)); MaxActionsToExecuteInParallel = Math.Min(MaxActionsToExecuteInParallel, MaxActionsAffordedByMemory); } MaxActionsToExecuteInParallel = Math.Max(1, Math.Min(MaxActionsToExecuteInParallel, MaxProcessorCount)); Log.TraceInformation("Performing {0} actions ({1} in parallel)", Actions.Count, MaxActionsToExecuteInParallel); Dictionary <Action, ActionThread> ActionThreadDictionary = new Dictionary <Action, ActionThread>(); int JobNumber = 1; using (ProgressWriter ProgressWriter = new ProgressWriter("Compiling C++ source code...", false)) { int ProgressValue = 0; while (true) { // Count the number of pending and still executing actions. int NumUnexecutedActions = 0; int NumExecutingActions = 0; foreach (Action Action in Actions) { ActionThread ActionThread = null; bool bFoundActionProcess = ActionThreadDictionary.TryGetValue(Action, out ActionThread); if (bFoundActionProcess == false) { NumUnexecutedActions++; } else if (ActionThread != null) { if (ActionThread.bComplete == false) { NumUnexecutedActions++; NumExecutingActions++; } } } // Update the current progress int NewProgressValue = Actions.Count + 1 - NumUnexecutedActions; if (ProgressValue != NewProgressValue) { ProgressWriter.Write(ProgressValue, Actions.Count + 1); ProgressValue = NewProgressValue; } // If there aren't any pending actions left, we're done executing. if (NumUnexecutedActions == 0) { break; } // If there are fewer actions executing than the maximum, look for pending actions that don't have any outdated // prerequisites. foreach (Action Action in Actions) { ActionThread ActionProcess = null; bool bFoundActionProcess = ActionThreadDictionary.TryGetValue(Action, out ActionProcess); if (bFoundActionProcess == false) { if (NumExecutingActions < Math.Max(1, MaxActionsToExecuteInParallel)) { // Determine whether there are any prerequisites of the action that are outdated. bool bHasOutdatedPrerequisites = false; bool bHasFailedPrerequisites = false; foreach (FileItem PrerequisiteItem in Action.PrerequisiteItems) { if (PrerequisiteItem.ProducingAction != null && Actions.Contains(PrerequisiteItem.ProducingAction)) { ActionThread PrerequisiteProcess = null; bool bFoundPrerequisiteProcess = ActionThreadDictionary.TryGetValue(PrerequisiteItem.ProducingAction, out PrerequisiteProcess); if (bFoundPrerequisiteProcess == true) { if (PrerequisiteProcess == null) { bHasFailedPrerequisites = true; } else if (PrerequisiteProcess.bComplete == false) { bHasOutdatedPrerequisites = true; } else if (PrerequisiteProcess.ExitCode != 0) { bHasFailedPrerequisites = true; } } else { bHasOutdatedPrerequisites = true; } } } // If there are any failed prerequisites of this action, don't execute it. if (bHasFailedPrerequisites) { // Add a null entry in the dictionary for this action. ActionThreadDictionary.Add(Action, null); } // If there aren't any outdated prerequisites of this action, execute it. else if (!bHasOutdatedPrerequisites) { ActionThread ActionThread = new ActionThread(Action, JobNumber, Actions.Count); JobNumber++; try { ActionThread.Run(); } catch (Exception ex) { throw new BuildException(ex, "Failed to start thread for action: {0} {1}\r\n{2}", Action.CommandPath, Action.CommandArguments, ex.ToString()); } ActionThreadDictionary.Add(Action, ActionThread); NumExecutingActions++; } } } } System.Threading.Thread.Sleep(TimeSpan.FromSeconds(LoopSleepTime)); } } Log.WriteLineIf(bLogDetailedActionStats, LogEventType.Console, "-------- Begin Detailed Action Stats ----------------------------------------------------------"); Log.WriteLineIf(bLogDetailedActionStats, LogEventType.Console, "^Action Type^Duration (seconds)^Tool^Task^Using PCH"); double TotalThreadSeconds = 0; // Check whether any of the tasks failed and log action stats if wanted. bool bSuccess = true; double TotalBuildProjectTime = 0; double TotalCompileTime = 0; double TotalCreateAppBundleTime = 0; double TotalGenerateDebugInfoTime = 0; double TotalLinkTime = 0; double TotalOtherActionsTime = 0; foreach (KeyValuePair <Action, ActionThread> ActionProcess in ActionThreadDictionary) { Action Action = ActionProcess.Key; ActionThread ActionThread = ActionProcess.Value; // Check for pending actions, preemptive failure if (ActionThread == null) { bSuccess = false; continue; } // Check for executed action but general failure if (ActionThread.ExitCode != 0) { bSuccess = false; } // Log CPU time, tool and task. double ThreadSeconds = Action.Duration.TotalSeconds; Log.WriteLineIf(bLogDetailedActionStats, LogEventType.Console, "^{0}^{1:0.00}^{2}^{3}^{4}", Action.ActionType.ToString(), ThreadSeconds, Path.GetFileName(Action.CommandPath), Action.StatusDescription, Action.bIsUsingPCH); // Update statistics switch (Action.ActionType) { case ActionType.BuildProject: TotalBuildProjectTime += ThreadSeconds; break; case ActionType.Compile: TotalCompileTime += ThreadSeconds; break; case ActionType.CreateAppBundle: TotalCreateAppBundleTime += ThreadSeconds; break; case ActionType.GenerateDebugInfo: TotalGenerateDebugInfoTime += ThreadSeconds; break; case ActionType.Link: TotalLinkTime += ThreadSeconds; break; default: TotalOtherActionsTime += ThreadSeconds; break; } // Keep track of total thread seconds spent on tasks. TotalThreadSeconds += ThreadSeconds; } Log.WriteLineIf(bLogDetailedActionStats, LogEventType.Console, "-------- End Detailed Actions Stats -----------------------------------------------------------"); // Log total CPU seconds and numbers of processors involved in tasks. Log.WriteLineIf(bLogDetailedActionStats || UnrealBuildTool.bPrintDebugInfo, LogEventType.Console, "Cumulative thread seconds ({0} processors): {1:0.00}", System.Environment.ProcessorCount, TotalThreadSeconds); // Log detailed stats Log.WriteLineIf(bLogDetailedActionStats || UnrealBuildTool.bPrintDebugInfo, LogEventType.Console, "Cumulative action seconds ({0} processors): {1:0.00} building projects, {2:0.00} compiling, {3:0.00} creating app bundles, {4:0.00} generating debug info, {5:0.00} linking, {6:0.00} other", System.Environment.ProcessorCount, TotalBuildProjectTime, TotalCompileTime, TotalCreateAppBundleTime, TotalGenerateDebugInfoTime, TotalLinkTime, TotalOtherActionsTime ); return(bSuccess); }