From 22ab1c4afd457eaeddafbbfb02be278cfe22854f Mon Sep 17 00:00:00 2001 From: Katy Coe Date: Sat, 14 Dec 2019 14:01:04 +0100 Subject: [PATCH] Performance: Paralellize code generation by namespace, assembly and class --- Il2CppDumper/Il2CppCSharpDumper.cs | 96 +++++++++++++++++++----------- 1 file changed, 60 insertions(+), 36 deletions(-) diff --git a/Il2CppDumper/Il2CppCSharpDumper.cs b/Il2CppDumper/Il2CppCSharpDumper.cs index deda284..bf61e74 100644 --- a/Il2CppDumper/Il2CppCSharpDumper.cs +++ b/Il2CppDumper/Il2CppCSharpDumper.cs @@ -8,6 +8,7 @@ using System.Linq; using System.Reflection; using System.Text; using System.Text.RegularExpressions; +using System.Threading.Tasks; using Il2CppInspector.Reflection; using CustomAttributeData = Il2CppInspector.Reflection.CustomAttributeData; using MethodInfo = Il2CppInspector.Reflection.MethodInfo; @@ -33,40 +34,44 @@ namespace Il2CppInspector private const string ExtAttribute = "System.Runtime.CompilerServices.ExtensionAttribute"; private const string DMAttribute = "System.Reflection.DefaultMemberAttribute"; + // Assembly attributes we have already emitted + private HashSet usedAssemblyAttributes = new HashSet(); + private readonly object usedAssemblyAttributesLock = new object(); + public Il2CppCSharpDumper(Il2CppModel model) => this.model = model; public void WriteSingleFile(string outFile) => WriteSingleFile(outFile, t => t.Index); - public void WriteSingleFile(string outFile, Func orderBy) => writeFile(outFile, model.Assemblies.SelectMany(x => x.DefinedTypes).OrderBy(orderBy)); + public void WriteSingleFile(string outFile, Func orderBy) { + usedAssemblyAttributes.Clear(); + writeFile(outFile, model.Assemblies.SelectMany(x => x.DefinedTypes).OrderBy(orderBy)); + } public void WriteFilesByNamespace(string outPath, Func orderBy, bool flattenHierarchy) { - var namespaces = model.Assemblies.SelectMany(x => x.DefinedTypes).GroupBy(t => t.Namespace); - var usedAssemblyAttributes = new HashSet(); - foreach (var ns in namespaces) { - writeFile($"{outPath}\\{(!string.IsNullOrEmpty(ns.Key)? ns.Key : "global").Replace('.', flattenHierarchy? '.' : '\\')}.cs", - ns.OrderBy(orderBy), excludedAssemblyAttributes: usedAssemblyAttributes); - usedAssemblyAttributes.UnionWith(ns.Select(t => t.Assembly).Distinct().SelectMany(a => a.CustomAttributes)); - } + usedAssemblyAttributes.Clear(); + Parallel.ForEach(model.Assemblies.SelectMany(x => x.DefinedTypes).GroupBy(t => t.Namespace), ns => { + writeFile($"{outPath}\\{(!string.IsNullOrEmpty(ns.Key) ? ns.Key : "global").Replace('.', flattenHierarchy ? '.' : '\\')}.cs", + ns.OrderBy(orderBy)); + }); } public void WriteFilesByAssembly(string outPath, Func orderBy) { - foreach (var asm in model.Assemblies) { + usedAssemblyAttributes.Clear(); + Parallel.ForEach(model.Assemblies, asm => { // Sort namespaces into alphabetical order, then sort types within the namespaces by the specified sort function writeFile($"{outPath}\\{asm.ShortName}.cs", asm.DefinedTypes.OrderBy(t => t.Namespace).ThenBy(orderBy)); - } + }); } public void WriteFilesByClass(string outPath, bool flattenHierarchy) { - var usedAssemblyAttributes = new HashSet(); - foreach (var type in model.Assemblies.SelectMany(x => x.DefinedTypes)) { - writeFile($"{outPath}\\" + (type.Namespace + (type.Namespace.Length > 0? "." : "") + Regex.Replace(type.Name, "`[0-9]", "")) - .Replace('.', flattenHierarchy ? '.' : '\\') + ".cs", - new[] {type}, excludedAssemblyAttributes: usedAssemblyAttributes); - usedAssemblyAttributes.UnionWith(type.Assembly.CustomAttributes); - } + usedAssemblyAttributes.Clear(); + Parallel.ForEach(model.Assemblies.SelectMany(x => x.DefinedTypes), type => { + writeFile($"{outPath}\\" + (type.Namespace + (type.Namespace.Length > 0 ? "." : "") + Regex.Replace(type.Name, "`[0-9]", "")) + .Replace('.', flattenHierarchy ? '.' : '\\') + ".cs",new[] {type}); + }); } - private void writeFile(string outFile, IEnumerable types, bool useNamespaceSyntax = true, IEnumerable excludedAssemblyAttributes = null) { + private void writeFile(string outFile, IEnumerable types, bool useNamespaceSyntax = true) { var nsRefs = new HashSet(); var code = new StringBuilder(); @@ -156,40 +161,59 @@ namespace Il2CppInspector Directory.CreateDirectory(Path.GetDirectoryName(outFile)); // Create output file - using StreamWriter writer = new StreamWriter(new FileStream(outFile, FileMode.Create), Encoding.UTF8); + bool fileWritten = false; + do { + try { + using StreamWriter writer = new StreamWriter(new FileStream(outFile, FileMode.Create), Encoding.UTF8); - // Write preamble - writer.Write(@"/* + // Write preamble + writer.Write(@"/* * Generated code file by Il2CppInspector - http://www.djkaty.com - https://github.com/djkaty */ "); - // Output using directives - writer.Write(string.Concat(usings.Select(n => $"using {n};\n"))); - if (nsRefs.Any()) - writer.Write("\n"); + // Output using directives + writer.Write(string.Concat(usings.Select(n => $"using {n};\n"))); + if (nsRefs.Any()) + writer.Write("\n"); - // Output assembly information and attributes - writer.Write(generateAssemblyInfo(assemblies, nsRefs, excludedAssemblyAttributes) + "\n\n"); + // Output assembly information and attributes + writer.Write(generateAssemblyInfo(assemblies, nsRefs) + "\n\n"); - // Output type definitions - writer.Write(code); + // Output type definitions + writer.Write(code); + + fileWritten = true; + } + catch (IOException ex) { + // If we get "file is in use by another process", we are probably writing a duplicate class in another thread + // Wait a bit and try again + if ((uint) ex.HResult != 0x80070020) + throw; + + System.Threading.Thread.Sleep(100); + } + } while (!fileWritten); } - private string generateAssemblyInfo(IEnumerable assemblies, IEnumerable namespaces, IEnumerable excludedAttributes = null) { + private string generateAssemblyInfo(IEnumerable assemblies, IEnumerable namespaces) { var text = new StringBuilder(); foreach (var asm in assemblies) { text.Append($"// Image {asm.Index}: {asm.ShortName} - Assembly: {asm.FullName} - Types {asm.ImageDefinition.typeStart}-{asm.ImageDefinition.typeStart + asm.ImageDefinition.typeCount - 1}\n"); // Assembly-level attributes - text.Append(asm.CustomAttributes.Where(a => a.AttributeType.FullName != ExtAttribute) - .Except(excludedAttributes ?? new List()) - .OrderBy(a => a.AttributeType.Name) - .ToString(new Scope { Current = null, Namespaces = namespaces }, attributePrefix: "assembly: ", emitPointer: !SuppressMetadata, mustCompile: MustCompile)); - if (asm.CustomAttributes.Any()) - text.Append("\n"); + lock (usedAssemblyAttributesLock) { + text.Append(asm.CustomAttributes.Where(a => a.AttributeType.FullName != ExtAttribute) + .Except(usedAssemblyAttributes ?? new HashSet()) + .OrderBy(a => a.AttributeType.Name) + .ToString(new Scope { Current = null, Namespaces = namespaces }, attributePrefix: "assembly: ", emitPointer: !SuppressMetadata, mustCompile: MustCompile)); + if (asm.CustomAttributes.Any()) + text.Append("\n"); + + usedAssemblyAttributes.UnionWith(asm.CustomAttributes); + } } return text.ToString().TrimEnd(); }