Skip to content

Add generation of structured object file dumps - #73913

Merged
MichalStrehovsky merged 1 commit into
dotnet:mainfrom
MichalStrehovsky:mstat
Aug 15, 2022
Merged

Add generation of structured object file dumps#73913
MichalStrehovsky merged 1 commit into
dotnet:mainfrom
MichalStrehovsky:mstat

Conversation

@MichalStrehovsky

Copy link
Copy Markdown
Member

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

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.
@Suchiman

Suchiman commented Aug 14, 2022

Copy link
Copy Markdown
Contributor

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):

// ********** Types Total Size 101.020
System.Private.CoreLib 94.464
System.Private.TypeLoader 4.296
System.Private.DisabledReflection 1.264
System.Console 964
NativeConsole 32
// **********
// ********** Methods Total Size 253.564
System.Private.CoreLib 235.602
System.Console 7.752
System.Private.TypeLoader 4.763
System.Private.DisabledReflection 4.010
System.Private.CompilerGenerated 1.078
NativeConsole 359
// **********
// ********** Size By Namespace
System 108.721
System.Text 48.384
System.Collections.Generic 43.342
System.Numerics 28.672
System.Runtime 18.460
System.Threading 16.551
System.Globalization 12.994
System.Runtime.CompilerServices 12.280
System.IO 10.968
System.Buffers 8.751
Internal.Runtime.TypeLoader 4.045
Interop 3.895
Internal.Runtime 3.837
System.Collections.Concurrent 3.706
Entry[] 3.264
Internal.Runtime.CompilerHelpers 2.640
System.Text.Unicode 2.525
System.Diagnostics 2.342
System.Collections 1.966
System.Reflection 1.946
Internal.Reflection 1.809
System.Runtime.Loader 1.426
System.Runtime.InteropServices 1.344
System.Threading.Tasks 1.189
Internal.TypeSystem 864
Internal.NativeFormat 819
Internal.DeveloperExperience 786
Internal.Runtime.Augments 619
Internal.Reflection.Core.NonPortable 494
Internal 478
<Module> 423
Internal.Metadata.NativeFormat 421
Internal.Reflection.Augments 420
PerCoreLockedStacks[] 352
LockedStack[] 352
ThreadLocalArray[] 336
Internal.CompilerGenerated 335
Boxed_Enumerator 291
Microsoft.Win32.SafeHandles 260
Boxed_Entry 235
System.Diagnostics.Tracing 214
GenericMethodEntry[] 176
GenericTypeEntry[] 176
NamedTypeLookupResult[] 176
ThreadLocalNodeFinalizationHelper[] 176
Cctor[][] 176
Internal.Runtime.CompilerServices 168
Bucket[] 168
Cctor[] 168
BlockingRecord[] 168
System.Runtime.Serialization 96
System.Runtime.ExceptionServices 80
System.Runtime.ConstrainedExecution 68
System.Reflection.Runtime.General 32
// **********
// ********** Blobs Total Size 118.773
ArrayOfFrozenObjects 40.888
FieldRvaData 32.893
Blob 9.448
ExternalReferencesTable 6.000
GenericComposition 4.995
GenericTypesHashtable 3.779
MethodReadOnlyData 2.924
ReadyToRunHelper 2.147
BlockReflectionTypeMap 1.821
TypeGenericDictionary 1.624
ArrayOfEmbeddedPointers 1.120
InterfaceDispatchMap 1.042
NonGCStatics 898
GCStatics 880
DefaultConstructorMap 873
UnboxingStub 864
ReadyToRunHeader 808
EETypeOptionalFields 748
InterfaceDispatchCellSection 688
MethodGenericDictionary 664
GenericMethodsHashtable 618
GCStaticEEType 552
NativeLayoutInfo 515
GCStaticsPreInitData 448
StaticsInfoHashtable 230
ArrayMap 224
ReadyToRunGenericLookupFromType 216
FatFunctionPointer 192
ClassConstructorContextMap 184
TypeThreadStaticIndex 160
MethodAssociatedData 95
ArrayOfEmbeddedData 80
GenericTypesTemplateMap 33
ReadyToRunGenericLookupFromDictionary 29
TentativeInstanceMethod 25
TypeManagerIndirection 16
GenericMethodsTemplateMap 9
ModulesSection 8
Metadata 5
ResourceIndex 3
TypeMetadataMap 3
ReflectionInvokeMap 3
DelegateMarshallingStubMap 3
StructMarshallingStubMap 3
ReflectionFieldMap 3
ExactMethodInstantiations 3
GenericVirtualMethodTable 3
InterfaceGenericVirtualMethodTable 3
ReflectionVirtualInvokeMap 3
ResourceData 0
StackTraceMethodMapping 0
ModuleInitializerList 0
// **********

Code:

Details
usingMono.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.

@Suchiman

Suchiman commented Aug 14, 2022

Copy link
Copy Markdown
Contributor

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

Copy link
Copy Markdown
MemberAuthor

I've played around with this a bit

Nice tool!!!

which is sums up to only half of the executable size

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 .reloc section that is fixups for things in .mstat. But the size is roughly proportional.

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!

Oh nice, I didn't know they made it public!

Looks like 109KB of these 967KB are just bitonic sort / vxsort in the GC...

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

Copy link
Copy Markdown
Member

We could build yet another flavor of the GC, but we already have 3 (wks, server, server+guardcf) and we need to stop somewhere.

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.

@MichalStrehovsky

Copy link
Copy Markdown
MemberAuthor

@sbomer@LakshanF could you have a look?

@tlakollotlakollo left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM! Thank you :D
:shipit:

@MichalStrehovsky
MichalStrehovsky merged commit 00f34fb into dotnet:mainAug 15, 2022
@MichalStrehovsky
MichalStrehovsky deleted the mstat branch August 15, 2022 21:55
@ghostghost locked as resolved and limited conversation to collaborators Sep 15, 2022
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants

@MichalStrehovsky@Suchiman@jkotas@tlakollo