Uh oh!
There was an error while loading. Please reload this page.
Add generation of structured object file dumps - #73913
Conversation
The compiler can currently emit an XML file with everything that was emitted into the output object file. This works and it's easily human readable. But the captured information is not very structured and cannot be used to generate aggregate information like 'this assembly contributed X bytes". This adds another dumper format similar to what we do for MIBC and others: it's ECMA-335 based and uses ldtoken/ldstr/ldc to encode the data. This can be used to build better tools. There's currently none but ILDASM, but it would be good to have it for 7.0. Good hackathon project.
I've played around with this a bit, for the smallest possible Hello World (967kb), i could get following numbers out (which is sums up to only half of the executable size): Code: DetailsusingMono.Cecil;usingMono.Cecil.Rocks;namespaceNativeAOTSizeAnalyzer;internalclassProgram{staticvoidMain(string[]args){varasm=AssemblyDefinition.ReadAssembly(@"something.mstat");varglobalType=(TypeDefinition)asm.MainModule.LookupToken(0x02000001);vartypes=globalType.Methods.First(x =>x.Name=="Types");vartypeStats=GetTypes(types).ToList();vartypeSize=typeStats.Sum(x =>x.Size);vartypesByModules=typeStats.GroupBy(x =>x.Type.Scope).Select(x =>new{x.Key.Name,Sum=x.Sum(x =>x.Size)}).ToList();Console.WriteLine($"// ********** Types Total Size {typeSize:n0}");foreach(varmintypesByModules.OrderByDescending(x =>x.Sum)){Console.WriteLine($"{m.Name,-40}{m.Sum,7:n0}");}Console.WriteLine($"// **********");Console.WriteLine();varmethods=globalType.Methods.First(x =>x.Name=="Methods");varmethodStats=GetMethods(methods).ToList();varmethodSize=methodStats.Sum(x =>x.Size+x.GcInfoSize+x.EhInfoSize);varmethodsByModules=methodStats.GroupBy(x =>x.Method.DeclaringType.Scope).Select(x =>new{x.Key.Name,Sum=x.Sum(x =>x.Size+x.GcInfoSize+x.EhInfoSize)}).ToList();Console.WriteLine($"// ********** Methods Total Size {methodSize:n0}");foreach(varminmethodsByModules.OrderByDescending(x =>x.Sum)){Console.WriteLine($"{m.Name,-40}{m.Sum,7:n0}");}Console.WriteLine($"// **********");Console.WriteLine();stringFindNamespace(TypeReferencetype){varcurrent=type;while(true){if(!String.IsNullOrEmpty(current.Namespace)){returncurrent.Namespace;}if(current.DeclaringType==null){returncurrent.Name;}current=current.DeclaringType;}}varmethodsByNamespace=methodStats.Select(x =>newTypeStats{Type=x.Method.DeclaringType,Size=x.Size+x.GcInfoSize+x.EhInfoSize}).Concat(typeStats).GroupBy(x =>FindNamespace(x.Type)).Select(x =>new{x.Key,Sum=x.Sum(x =>x.Size)}).ToList();Console.WriteLine($"// ********** Size By Namespace");foreach(varminmethodsByNamespace.OrderByDescending(x =>x.Sum)){Console.WriteLine($"{m.Key,-40}{m.Sum,7:n0}");}Console.WriteLine($"// **********");Console.WriteLine();varblobs=globalType.Methods.First(x =>x.Name=="Blobs");varblobStats=GetBlobs(blobs).ToList();varblobSize=blobStats.Sum(x =>x.Size);Console.WriteLine($"// ********** Blobs Total Size {blobSize:n0}");foreach(varminblobStats.OrderByDescending(x =>x.Size)){Console.WriteLine($"{m.Name,-40}{m.Size,7:n0}");}Console.WriteLine($"// **********");}publicstaticIEnumerable<TypeStats>GetTypes(MethodDefinitiontypes){types.Body.SimplifyMacros();varil=types.Body.Instructions;for(inti=0;i+2<il.Count;i+=2){vartype=(TypeReference)il[i+0].Operand;varsize=(int)il[i+1].Operand;yieldreturnnewTypeStats{Type=type,Size=size};}}publicstaticIEnumerable<MethodStats>GetMethods(MethodDefinitionmethods){methods.Body.SimplifyMacros();varil=methods.Body.Instructions;for(inti=0;i+4<il.Count;i+=4){varmethod=(MethodReference)il[i+0].Operand;varsize=(int)il[i+1].Operand;vargcInfoSize=(int)il[i+2].Operand;varehInfoSize=(int)il[i+3].Operand;yieldreturnnewMethodStats{Method=method,Size=size,GcInfoSize=gcInfoSize,EhInfoSize=ehInfoSize};}}publicstaticIEnumerable<BlobStats>GetBlobs(MethodDefinitionblobs){blobs.Body.SimplifyMacros();varil=blobs.Body.Instructions;for(inti=0;i+2<il.Count;i+=2){varname=(string)il[i+0].Operand;varsize=(int)il[i+1].Operand;yieldreturnnewBlobStats{Name=name,Size=size};}}}publicclassTypeStats{publicTypeReferenceType{get;set;}publicintSize{get;set;}}publicclassMethodStats{publicMethodReferenceMethod{get;set;}publicintSize{get;set;}publicintGcInfoSize{get;set;}publicintEhInfoSize{get;set;}}publicclassBlobStats{publicstringName{get;set;}publicintSize{get;set;}}Edit (2023-01-23 @MichalStrehovsky): updated the parser to account for the file format change in #75328. |
Btw. dunno if you're aware, i've just discovered https://devblogs.microsoft.com/performance-diagnostics/sizebench-a-new-tool-for-analyzing-windows-binary-size/ which seems quite neat! Looks like 109KB of these 967KB are just bitonic sort / vxsort in the GC... |
MichalStrehovsky
commented
Aug 14, 2022
Nice tool!!!
Yeah, this is more meaningful for bigger apps than the absolute minimal app. The absolute minimal app is dominated by the native parts of the binary (GC, vxsort tables, etc.) One thing .mstat doesn't capture is relocations - the NativeAOT data structures are pointer-heavy, so there's a lot of data in the
Oh nice, I didn't know they made it public!
Yeah, I disable all that in bflat. It's one of the reasons why it's smaller than stock NativeAOT. We build two flavors of the GC for NativeAOT - one that only has the workstation GC, and one that has server. I thought about disabling vxsort for the workstation one, but I don't have any data to base the decision on (besides size). We want to have comparable perf between CoreCLR-JIT and CoreCLR-NativeAOT. We could build yet another flavor of the GC, but we already have 3 (wks, server, server+guardcf) and we need to stop somewhere. |
jkotas
commented
Aug 15, 2022
A potential solution for this is to add an option for building GC from sources as part of application publish, similar to what's done in #72896 for ICU shims. It would allow you to build the GC with any custom settings you wish. |
The compiler can currently emit an XML file with everything that was emitted into the output object file. This works and it's easily human readable. But the captured information is not very structured and cannot be used to generate aggregate information like 'this assembly contributed X bytes".
This adds another dumper format similar to what we do for MIBC and others: it's ECMA-335 based and uses ldtoken/ldstr/ldc to encode the data.
This can be used to build better tools. There's currently none but ILDASM, but it would be good to have it for 7.0. Good hackathon project.
Cc @dotnet/ilc-contrib