Skip to content

Add multiple files compilation mode for crossgen2 - #37411

Closed
gbalykov wants to merge 1 commit into
dotnet:mainfrom
gbalykov:crossgen2-pipeline
Closed

Add multiple files compilation mode for crossgen2#37411
gbalykov wants to merge 1 commit into
dotnet:mainfrom
gbalykov:crossgen2-pipeline

Conversation

@gbalykov

Copy link
Copy Markdown
Member

By using --out-near-input and --single-file-compilation options multiple files can be compiled in one invocation of crossgen2. This allows to remove startup overhead if many files are to be compiled anyway.

x64, f95b2b2, release build, 100 measurements for each case (for single-file compilation mode all 100 copies of file are passed in one command):

./corerun `pwd`/crossgen2/crossgen2.dll /tmp/crossgen2.dll -O -r:`pwd`/* --out-near-input --single-file-compilation

default number of threads

Basically, the result is a constant ~0.3s diff per dll:

before:
crossgen2.dll: 0.80555s
System.Private.CoreLib.dll: 3.15564s
after:
crossgen2.dll: 0.54847s (-31.9%, -0.26s)
System.Private.CoreLib.dll: 2.80951s (-11.0%, -0.35s) 

1 thread

before:
crossgen2.dll: 0.88498s
System.Private.CoreLib.dll: 8.32537s
after:
crossgen2.dll: 0.53211s (-39.9%, -0.35s)
System.Private.CoreLib.dll: 7.74492s (-7.0%, -0.58s) 

cc @alpencolt

@MichalStrehovsky

Copy link
Copy Markdown
Member

Did you run crossgen on crossgen2 itself before doing the measurements? Without R2R code, a significant portion of time will be spent JITting the compiler.

Compilers (clang, Roslyn, VC++, you name it) don't offer multiple input/multiple output modes because they're not useful when integrating into build systems. Multiple-input/multiple-output is a test hook. We place test hooks in the src\coreclr\src\tools\r2rtest runner so that we don't have test hooks in the shipping compiler. r2rtest already has modes to compile all files in a directory.

We could potentially add a new launch option that doesn't create a new compilation process, but does an Assembly.Load to load the crossgen2 entrypoint assembly if this is really important. Once the assembly is loaded it can do asm.EntryPoint.Invoke(...) to invoke the entrypoint in the context of the current process.

Note that the crossgen2 compiler has known "memory leaks" when run in such mode and will run out of memory eventually (there are static fields that cache state that is only relevant to a single compilation and is never released until the process dies).

Cc @dotnet/crossgen-contrib

@gbalykov

gbalykov commented Jun 4, 2020

Copy link
Copy Markdown
MemberAuthor

Yes, forgot to mention that, all system libs and crossgen2 libs are compiled with first crossgen in r2r mode. However, runtime still spends much time on jitting, even with r2r images. Change in this PR allows to mitigate startup overhead of jitting and loading crossgen2.

Our concern is that on small dlls crossgen2 perf is much worse than first crossgen (for example, on more that 1100% for crossgen2.dll on x64). For large dlls like SPC.dll crossgen2 is better on 25% on x64, however this is achieved with 16 threads, and with 1 thread crossgen2 is 2x times slower.

On arm devices with 2 cpus, however, we can't launch so many threads, so crossgen2 is slower than first crossgen even on SPC.dll.

Here's some data:

x64

dllthreadscrossgen, scrossgen2, sdiff(%)
crossgen2.dlldefault0.065080.80555+1138
10.065080.88498+1260
20.85827+1219
40.81757+1172
80.8094+1144
160.80782+1141
320.80494+1137
System.Private.CoreLib.dlldefault4.189263.15564-24.7
14.189268.32537+98.7
25.55454+32.6
44.14622-1.0
83.3941-19.0
163.12814-25.3
323.17038-24.3

armel

dllthreadscrossgen, scrossgen2, sdiff(%)
crossgen2.dlldefault0.568847.10501+1149
10.568847.21069+1167
26.94721+1121
46.88047+1110
86.80611+1096
System.Private.CoreLib.dlldefault43.1077
143.107788.631+105.6
261.0679+41.7
461.4447+42.5
861.9229+43.6

@davidwrighton

Copy link
Copy Markdown
Member

@gbalykov, thank you for looking deeper into this. I agree that it is quite concerning how slow crossgen2 is, especially for the smaller binaries. There are several details to note.

  1. Performance of the compiler running on the armel platform is very slow, and I assume that is significant in terms of engineering cost for your team. Have you considered building a cross-targetting jit so that you can run the armel compiles on an x64 machine, or does that not fit with your engineering strategy? Based on the numbers from your most recent post here, that would be a better time improvement than making this change to the crossgen2 compilation model? We have done some experiments with an arm64 targetting cross compiler and found that we were easily able to produce binary identical images while running on an x64 host.

  2. @MichalStrehovsky This change in compilation model does have precedent in the .NET ecosystem. In particular, Roslyn actually is typically run in a mode which uses a compiler server which amortizes the cost of jitting the Roslyn codebase across multiple compilations. In that mode, there is a compiler server which does the actual compilations, and csc just serves to pass command line arguments to that persistent server. That preserves the illusion that the compiler has a single output model to the build system, but also allows for multiple processes to take advantage of the benefits of running the compiler without needing excess jitting. Of course, this sort of server model requires some careful engineering to make sure that repeated compilations do not interfere with each others, etc.

  3. We expect that the composite build mode of crossgen2 may provide benefits for this sort of situation, such that a R2R variant of crossgen2 may be closer in performance to crossgen. We don't have good numbers for that yet, but in the next couple of months we hope to get them.

  4. Given the above, and especially point 2, I'm tempted to suggest that if you are unable to use a cross compilation approach, instead of building in new command line options, we would be more likely to accept a patch, which moved much of this processing in crossgen2.dll into ILCompiler.ReadyToRun.dll, such that it could be run by either crossgen2 as an application, or used directly by some sort of wrapper or server process that would be able to achieve these sorts of performance wins. I think that's a bit more work, but long term, I believe the compiler server approach would be better than a series of new command line options as is proposed here.

@MichalStrehovsky

Copy link
Copy Markdown
Member

This change in compilation model does have precedent in the .NET ecosystem. In particular, Roslyn actually is typically run in a mode which uses a compiler server which amortizes the cost of jitting the Roslyn codebase across multiple compilations.

Yes, I'm aware of that mode. That's why I keep pushing for people to stop using statics to store per compilation state but people keep adding those when I'm not looking. But there's a difference between a compiler server and a command line argument to batch compile. The former can be integrated into build systems (but it does bring it's own challenges which is why I sometimes need to taskkill dotnet process before I can do git clean on the runtime repo); the latter cannot be integrated into build systems and cannot be a shipping switch.

I know what sort of response I get for this, but I compiled crossgen2 with the CoreRT compiler and compared throughput with the CoreCLR/ReadyToRun based one:

BeforeAfterImprovement
Compile System.Private.CoreLib3446 ms2632 ms23%
Compile Hello World452 ms68 ms85%

We'll probably need to build a compilation server to get throughput anywhere near this as long as the compiler is hosted on top of CoreCLR.

@MichalStrehovsky

Copy link
Copy Markdown
Member

We expect that the composite build mode of crossgen2 may provide benefits for this sort of situation

When we were discussing publishing options for crossgen2, we ruled out self-contained publishing because the size was prohibitively large - I don't think we'll be able to composite-compile crossgen2 itself and ship it that way to our customers. We can use it to speed up our inner loop, but I'm skeptical of our ability to pass the benefit to our customers.

@alpencolt

Copy link
Copy Markdown

We've faced with crossgen2 throughput on compiling tests, @davidwrighton one of the ways to solve it using cross compilation, and it should already work by using: https://github.com/dotnet/runtime/tree/master/src/coreclr/src/jit/armelnonjit

But there is another case it's when user install application from market to device. On this scenario we cannot use cross compilation. This PR or server mode will help. In case of server mode it should be easily started and shot down.

@davidwrighton

Copy link
Copy Markdown
Member

Ah, as I understand it, this switch is intended for use outside of build system driven scenarios, and only for use within a bespoke application installer pipeline built by your company. This isn't a scenario that has been considered actively as part of crossgen2 development.

Could you share the amount of improvement that you are seeing as a result of this change to the end to end install time of typical application?

@ViktorHofer

Copy link
Copy Markdown
Member

// Auto-generated message

69e114c which was merged 12/7 removed the intermediate src/coreclr/src/ folder. This PR needs to be updated as it touches files in that directory which causes conflicts.

To update your commits you can use this bash script: https://gist.github.com/ViktorHofer/6d24f62abdcddb518b4966ead5ef3783. Feel free to use the comment section of the gist to improve the script for others.

@danmoseley

Copy link
Copy Markdown
Contributor

@davidwrighton this PR seems to have been waiting on an update for 7 months - would it make sense to close it if it's not actively being worked on?

@davidwrighton

Copy link
Copy Markdown
Member

Yes, I think that makes sense. @alpencolt if you are still interested in this, please re-activate this PR/provide some of the performance numbers we were looking for.

@alpencolt

Copy link
Copy Markdown

@davidwrighton we're working right now on this, I hope we'll share results for crossgen vs crossgen2 perfromance and memory comparison on armel in this week.

@gbalykov

Copy link
Copy Markdown
MemberAuthor

Could you share the amount of improvement that you are seeing as a result of this change to the end to end install time of typical application?

Sorry for the late response. I've measured Calculator app as a typical Tizen Xamarin app. Its installation (without ni compilation) takes just 3.656 seconds.

Target arm device has just two cpus, so crossgen2 results for >=2 threads are pretty much the same.

crossgen typethreadsCalculator app compilation time (7 dlls), secondsdiff with crossgen1
crossgen1default=117.552x1.0
crossgen2 with -O option159.828x3.41
crossgen2 with -O option248.301x2.75
crossgen2 with -O option, pipeline136.179x2.06
crossgen2 with -O option, pipeline229.965x1.70

As you can see, pipeline mode saves 18.3 (38%) and 23.6 (39%) seconds for 2 and 1 threads respectively. Considering app installation time, pipeline mode saves 35-37% of end-to-end app install time. Without these changes crossgen2 is almost 3 times slower than crossgen1.

Additionally, I've measured system libs recompilation from scratch using crossgen1 and crossgen2.

crossgen typethreads261 dlls time, secondsdiff with crossgen1
crossgen1default=1316.08x1.0
crossgen2 with -O option11979x6.26
crossgen2 with -O option21791.72x5.67

Unfortunately, pipeline mode leaks memory which results in process getting killed by oom killer. So, I wasn't able to measure all 261 system libs compilation in one command. Anyway, currently it takes ~5mins for crossgen1 to compile all system libs and ~30mins for crossgen2.

cc @alpencolt

@davidwrighton

Copy link
Copy Markdown
Member

I've re-opened the request, as there is active work happening here. Could you clarify if the crossgen2 binaries in this test were themselves crossgenned? Also, could you describe what version of crossgen2 is in use here? Is it from the 5.0 release branch, or a recent build from the master branch?

@nattress, @mangod9 We need to come up with a solution here of some form. In my opinion a slowdown of 2.75X is really not acceptable. I dislike the approach taken here, but it is very expedient, and not particularly impacting to the scenarios we use here in the more general .NET community.

@alpencolt@gbalykov you mention that pipeline mode leaks memory. Do you know what it is leaking, and by how much?

@gbalykov

Copy link
Copy Markdown
MemberAuthor

This is measured on dotnet/runtime master (6.0), commit d266fdb. In application related measurements above, all system libs including crossgen2 are compiled in r2r. In "system libs recompilation from scratch" scenario no libs are compiled, all libs are compiled from scratch, order of libs compilation is System.Private.CoreLib.dll, all 5 crossgen2 dlls, others in some order.

I'm not yet sure what is leaking in pipeline mode, but this resulted in ~800 Mb of physical memory occupied for approximately 127 compiled dlls. Then oom killer killed the process. This is the patch that I've used: 1.patch.txt.

@gbalykov

Copy link
Copy Markdown
MemberAuthor

Regarding memory consumption, here's memory consumption of System.Private.CoreLib.dll compilation (when all system libs are compiled in r2r):

crossgen typethreadsSIZE, kbPSS, kbRSS, kb
crossgen1default=1760244344244636
crossgen2 -O1354956131119137672
crossgen2 -O2342544137495144436

Crossgen2 RSS is more than 3 times higher.

For Tizen Xamarin app, mentioned above, crossgen2 RSS is ~2 times higher.

dll, compiled with crossgen1SIZE, kbPSS, kbRSS, kb
Calculator.dll601762369224888
Tizen.Wearable.CircularUI.Forms.Renderer.dll510481976020764
Tizen.Wearable.CircularUI.Forms.dll539801819919400
Xamarin.Forms.Core.dll624323000431200
Xamarin.Forms.Platform.Tizen.dll586362207623272
Xamarin.Forms.Platform.dll484481365214804
Xamarin.Forms.Xaml.dll531561720718408
dll, compiled with crossgen2 -O 2 threadsSIZE, kbPSS, kbRSS, kb
Calculator.dll2482323594939152
Tizen.Wearable.CircularUI.Forms.Renderer.dll2461443659039824
Tizen.Wearable.CircularUI.Forms.dll2493883621739416
Xamarin.Forms.Core.dll2759206579869312
Xamarin.Forms.Platform.Tizen.dll2500445177055184
Xamarin.Forms.Platform.dll2454882817530952
Xamarin.Forms.Xaml.dll2473563654939800

@danmoseley

Copy link
Copy Markdown
Contributor

@mangod9 could you please set an assignee on this PR ? It helps for old PR's to have a "shepherd" and this is the oldest one without such a person..

@danmoseley

Copy link
Copy Markdown
Contributor

@mangod9 thoughts about owner?

@mangod9

Copy link
Copy Markdown
Member

Sorry, must have missed the previous tag. Adding @trylek as well. We will discuss how to proceed in this week.

Base automatically changed from master to mainMarch 1, 2021 09:06
- Add --out-near-input option, which adds .ni. suffix to input filepath
and stores resulting ni.dll near original dll. In this mode --out option can be skipped.
- Add --single-file-compilation mode, which allows to compile all input files separately.
@gbalykovgbalykov reopened this Mar 29, 2021
@mangod9

Copy link
Copy Markdown
Member

Hey @gbalykov. assume this PR is still relevant? If you could resolve conflicts we can work on getting it merged. Just a note that as part of the regular workflow we wouldnt be validating the multiple file compilation mode.

@gbalykov

Copy link
Copy Markdown
MemberAuthor

@mangod9 yes, this is still relevant, I'll rebase it

@mangod9

Copy link
Copy Markdown
Member

Hi @gbalykov, closing this for now, please reopen when its ready for review? Reminder that preview7 (early July) is when new feature work should be done for .net 6. Thanks.

@mangod9mangod9 closed this May 10, 2021
@gbalykov

Copy link
Copy Markdown
MemberAuthor

@mangod9 thanks. This PR is obsolete, #51154 is created instead

@karelzkarelz added this to the 6.0.0 milestone May 20, 2021
@ghostghost locked as resolved and limited conversation to collaborators Jun 19, 2021
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.

9 participants

@gbalykov@MichalStrehovsky@davidwrighton@alpencolt@ViktorHofer@danmoseley@mangod9@karelz@Dotnet-GitSync-Bot