Browse Source

Explicitly create partitioners for `Parallel.ForEach` calls.

Apparently the default `Parallel.ForEach` logic checks whether the input enumerable is an array or `IList<T>`,
and if it is, creates whole chunks of sequential list/array indices and assigns those to the worker threads.

This is more efficient if the individual items are processed very quickly; but if they take varying amounts of time, a single chunk full of expensive items might keep a single CPU core busy for long after all other CPU cores have gone idle.
pull/2087/head
Daniel Grunwald 5 years ago
parent
commit
d02fd09822
  1. 3
      ICSharpCode.Decompiler/CSharp/ProjectDecompiler/WholeProjectDecompiler.cs
  2. 44
      ILSpy/Commands/DecompileAllCommand.cs
  3. 9
      ILSpy/Commands/DisassembleAllCommand.cs
  4. 7
      ILSpy/SolutionWriter.cs

3
ICSharpCode.Decompiler/CSharp/ProjectDecompiler/WholeProjectDecompiler.cs

@ -33,6 +33,7 @@ using static ICSharpCode.Decompiler.Metadata.MetadataExtensions; @@ -33,6 +33,7 @@ using static ICSharpCode.Decompiler.Metadata.MetadataExtensions;
using ICSharpCode.Decompiler.Metadata;
using ICSharpCode.Decompiler.Solution;
using ICSharpCode.Decompiler.DebugInfo;
using System.Collections.Concurrent;
namespace ICSharpCode.Decompiler.CSharp.ProjectDecompiler
{
@ -205,7 +206,7 @@ namespace ICSharpCode.Decompiler.CSharp.ProjectDecompiler @@ -205,7 +206,7 @@ namespace ICSharpCode.Decompiler.CSharp.ProjectDecompiler
var progress = ProgressIndicator;
DecompilerTypeSystem ts = new DecompilerTypeSystem(module, AssemblyResolver, Settings);
Parallel.ForEach(
files,
Partitioner.Create(files, loadBalance: true),
new ParallelOptions {
MaxDegreeOfParallelism = this.MaxDegreeOfParallelism,
CancellationToken = cancellationToken

44
ILSpy/Commands/DecompileAllCommand.cs

@ -19,6 +19,7 @@ @@ -19,6 +19,7 @@
#if DEBUG
using System;
using System.Collections.Concurrent;
using System.Diagnostics;
using System.Linq;
using System.Threading.Tasks;
@ -40,29 +41,32 @@ namespace ICSharpCode.ILSpy @@ -40,29 +41,32 @@ namespace ICSharpCode.ILSpy
{
Docking.DockWorkspace.Instance.RunWithCancellation(ct => Task<AvalonEditTextOutput>.Factory.StartNew(() => {
AvalonEditTextOutput output = new AvalonEditTextOutput();
Parallel.ForEach(MainWindow.Instance.CurrentAssemblyList.GetAssemblies(), new ParallelOptions { MaxDegreeOfParallelism = Environment.ProcessorCount, CancellationToken = ct }, delegate(LoadedAssembly asm) {
if (!asm.HasLoadError) {
Stopwatch w = Stopwatch.StartNew();
Exception exception = null;
using (var writer = new System.IO.StreamWriter("c:\\temp\\decompiled\\" + asm.ShortName + ".cs")) {
try {
new CSharpLanguage().DecompileAssembly(asm, new Decompiler.PlainTextOutput(writer), new DecompilationOptions() { FullDecompilation = true, CancellationToken = ct });
Parallel.ForEach(
Partitioner.Create( MainWindow.Instance.CurrentAssemblyList.GetAssemblies(), loadBalance: true),
new ParallelOptions { MaxDegreeOfParallelism = Environment.ProcessorCount, CancellationToken = ct },
delegate(LoadedAssembly asm) {
if (!asm.HasLoadError) {
Stopwatch w = Stopwatch.StartNew();
Exception exception = null;
using (var writer = new System.IO.StreamWriter("c:\\temp\\decompiled\\" + asm.ShortName + ".cs")) {
try {
new CSharpLanguage().DecompileAssembly(asm, new Decompiler.PlainTextOutput(writer), new DecompilationOptions() { FullDecompilation = true, CancellationToken = ct });
}
catch (Exception ex) {
writer.WriteLine(ex.ToString());
exception = ex;
}
}
catch (Exception ex) {
writer.WriteLine(ex.ToString());
exception = ex;
lock (output) {
output.Write(asm.ShortName + " - " + w.Elapsed);
if (exception != null) {
output.Write(" - ");
output.Write(exception.GetType().Name);
}
output.WriteLine();
}
}
lock (output) {
output.Write(asm.ShortName + " - " + w.Elapsed);
if (exception != null) {
output.Write(" - ");
output.Write(exception.GetType().Name);
}
output.WriteLine();
}
}
});
});
return output;
}, ct)).Then(output => Docking.DockWorkspace.Instance.ShowText(output)).HandleExceptions();
}

9
ILSpy/Commands/DisassembleAllCommand.cs

@ -23,6 +23,8 @@ using System.Diagnostics; @@ -23,6 +23,8 @@ using System.Diagnostics;
using System.Threading.Tasks;
using ICSharpCode.ILSpy.TextView;
using ICSharpCode.ILSpy.Properties;
using System.Collections.Concurrent;
namespace ICSharpCode.ILSpy
{
[ExportMainMenuCommand(Menu = nameof(Resources._File), Header = nameof(Resources.DEBUGDisassemble), MenuCategory = nameof(Resources.Open), MenuOrder = 2.5)]
@ -37,8 +39,11 @@ namespace ICSharpCode.ILSpy @@ -37,8 +39,11 @@ namespace ICSharpCode.ILSpy
{
Docking.DockWorkspace.Instance.RunWithCancellation(ct => Task<AvalonEditTextOutput>.Factory.StartNew(() => {
AvalonEditTextOutput output = new AvalonEditTextOutput();
Parallel.ForEach(MainWindow.Instance.CurrentAssemblyList.GetAssemblies(), new ParallelOptions { MaxDegreeOfParallelism = Environment.ProcessorCount, CancellationToken = ct }, delegate(LoadedAssembly asm) {
if (!asm.HasLoadError) {
Parallel.ForEach(
Partitioner.Create(MainWindow.Instance.CurrentAssemblyList.GetAssemblies(), loadBalance: true),
new ParallelOptions { MaxDegreeOfParallelism = Environment.ProcessorCount, CancellationToken = ct },
delegate (LoadedAssembly asm) {
if (!asm.HasLoadError) {
Stopwatch w = Stopwatch.StartNew();
Exception exception = null;
using (var writer = new System.IO.StreamWriter("c:\\temp\\disassembled\\" + asm.Text.Replace("(", "").Replace(")", "").Replace(' ', '_') + ".il")) {

7
ILSpy/SolutionWriter.cs

@ -99,7 +99,12 @@ namespace ICSharpCode.ILSpy @@ -99,7 +99,12 @@ namespace ICSharpCode.ILSpy
Stopwatch stopwatch = Stopwatch.StartNew();
try {
await Task.Run(() => Parallel.ForEach(assemblies, n => WriteProject(n, language, solutionDirectory, ct)))
// Explicitly create an enumerable partitioner here to avoid Parallel.ForEach's special cases for lists,
// as those seem to use static partitioning which is inefficient if assemblies take differently
// long to decompile.
await Task.Run(() => Parallel.ForEach(Partitioner.Create(assemblies),
new ParallelOptions { MaxDegreeOfParallelism = Environment.ProcessorCount, CancellationToken = ct },
n => WriteProject(n, language, solutionDirectory, ct)))
.ConfigureAwait(false);
await Task.Run(() => SolutionCreator.WriteSolutionFile(solutionFilePath, projects))

Loading…
Cancel
Save