How to prove that the .NET CLR JIT compiles every method only once per run?
Asked Answered
B

1

3

There's an old question asking whether C# is JIT compiled every time and the answer by famous Jon Skeet is: "no, it's compiled only once per application" as long as we're talking about desktop applications which are not NGENed.

I want to know if that information from 2009 is still true and I want to figure that out by experiment and debugging, potentially by putting a breakpoint on the JITter and using WinDbg commands to inspect objects and methods.

My research so far

I know that the .NET memory layout considers a Header (at address A-4) and a Method Table (at address A+0) per object before the actual data starts (at address A+4). So it would be possible that each object has a different method table and thus could have different JITted methods.

Why do I have doubts about the correctness of the statement?

We had a workshop for parallel programming and one claim by the trainer was that methods are JITted for every object per thread. That clearly didn't make sense to me and I was able to write a counter example application.

Unfortunately, the following other topics came up, for which I also want to write a demonstration:

  • new .NET frameworks
  • application domains
  • code access security

The linked answer was written when .NET 3.5 was released. It was not substantially changed since then, i.e. it has not received updates for .NET 4.0, 4.6 and 4.6.

Regarding application domains, my personal opinion is that I could unload an application domain, which unloads assemblies. If an assembly is unloaded, it's gone and the IL code goes with it. I don't see much benefit in keeping native code for IL code which was destroyed. Therefore, I could imagine that creating an application domain and loading the assembly again might result in JITting the method again.

Regarding code access security, I'm not sure if it is considered by the JIT compiler based on the current permissions or whether it's done by reflection at runtime. If it's done by the JIT compiler, the compiled code will differ, depending on the permission set.

Bailly answered 15/2, 2017 at 23:40 Comment(9)
My first thought has been that it's very useless that you've added a new Q&A when it could be enough that you comment out Jon Skeet's answer on that old Q&A to verify if that info is still valid...Trictrac
@MatíasFidemraizer: so you think I should add a comment on Jon Skeets answer and ask for clarification? IMHO I have already explained why it's not a duplicate. The answer from 2009 can only have considered .NET up to 3.5, I'm asking for newer versions as well.Bailly
I don't see how Code Access Security would change anything about how IL is turned into real code, and I don't think that new frameworks have changed the decision about when to JIT (but have introduced a new JIT compiler). But your point about app domains does seem to raise a genuine new aspect that's not been addressed.Graciagracie
It is not really obvious to me why this has to be proven. The claim is certainly not true (ducking bolt of lighting), generic code gets compiled multiple times by the jitter. One copy that handles any reference type argument and additional copies for each distinct value type argument. And sure, for AppDomains the LoaderOptimization applies. There are profiler callbacks that tells you what the jitter is working on, ICorProfilerCallback::JITCompilationStarted() and ICorProfilerCallback4::ReJITCompilationStarted().Thromboplastic
In addition to Hans' comments, as of .NET 4.5, a managed profiler can request that the runtime re-jitt a method (so that it can instrument it differently). There is also a case where the run time can start jitting a method it is already in the process of jitting.Galateah
use ETW to capture .net Jit events (https://mcmap.net/q/131586/-activate-stacks-only-for-some-specific-etw-tasks-in-a-provider). In the profile, replace 0x8008 with 0x8018 to also capture Jit events. now you see loader, jit and exception data with callstacks. now look when jit happensColleen
@ThomasWeller Just try to ask Jon Skeet to update his answer if there's something new in newer CLR versions.Trictrac
@MatíasFidemraizer: I did; see his comment on the linked answer. It seems he does not have enough knowledge to update.Bailly
@ThomasWeller I see. Actually who can answer you with 100% of accuracy can be a .net development team member...Trictrac
C
2

The "design principle" of the JIT is to compile a method (an instantiation of method for generic methods) once and and reuse the same native code. Of course, the implementation is extremely complicated and I'll try simplify the answer without compromising accuracy. The answer is the same for all versions of the runtime since .NET 2.0 and up to the latest .NET 4.6 (I don't know about .NET 1.x, probably the same).

The runtime profiler callbacks and the ETW events are poorly documented. Both of them occur when JIT compilation is attempted, but not necessarily succeeded. There are three cases where this can happen: 1- the method fails to meet certain security requirements, 2- the verification of the method failed, and 3- memory could not be allocated to hold the native code to be emitted. Therefore, the JIT start callback and event can overestimate the number of times a method has actually been compiled. Similarly, the JIT completion callback and event are inaccurate. There are few rarely occurring cases in which they might underestimate the number of times the same method has been successfully compiled. It's worth mentioning at this point that the # of Methods JITted performance counter reports accurately the number of times all IL methods have been compiled collectively in all appdomains of a process.

For appdomain-specific assemblies, methods are compiled in each appdomain separately. There is no sharing (even though it can be technically possible sometimes). For appdomain-neutral assemblies, the runtime attempts to compile each method once and share the native code with all appdomains.

How to prove that the .NET CLR JIT compiles every method only once per run?

Well, in some cases (such as when using background JIT, and other extremely subtle cases), a method can get compiled without ever getting executed. So saying every method is compiled once per run is inaccurate.

You can refer to the CoreCLR JIT source code for more information (the JIT is the same one used in the .NET framework 4.5+ but this answer applies to older versions since the JIT triggering mechanism is mostly the same). The source code is the proof.

methods are JITted for every object per thread

Yes, that doesn't make any sense. The scope of compilation is appdomains.

Chon answered 3/3, 2017 at 19:13 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.