Search code examples
c#.net-coredllclrjit

When the JIT Compiler for .Net 6.0 loads a .dll mid-execution, does it load the entire .dll in-memory, or only the methods needed?


Our programs are running in an environment where it is bad to be wasteful of RAM. There are a large number of large 3rd-party packages we could use in our code that would make writing the code somewhat easier, but only a few of the methods in those packages are of interest to us, meaning most of the contents of the 3rd-party library .dll's would be code that, if loaded/compiled to RAM, would be a waste of memory.

Other places I have looked indicate clearly that for the main executable the JIT compiler compiles to machine code on a per-method basis as methods are actually used; and other places indicate clearly that assemblies are only loaded at the moment they are needed; but nothing I have seen yet explains the intersection of those two questions - Specifically, once a .dll assembly needs to be loaded mid-execution, does the CLR and JIT compiler perform optimizations so that only the parts of that .dll that actually need to be used are loaded into memory, or is the whole .dll loaded/compiled into memory at once?

If the answer is that only the methods actually called in the .dll are loaded/compiled, how can the runtime know which methods it needs to call without first loading the whole .dll into memory? Does it do two steps, one to load the whole .dll and examine it to find out which parts are needed, and then in a different step it takes the chunks of the .dll that are needed and compiles those, dropping the rest of the .dll out of memory?

How does this behavior differ if the .dll is managed code vs. unmanaged code?

How does this behavior differ if we use the "Produce Single File" publish setting in Visual Studio?


Solution

  • once a .dll assembly needs to be loaded mid-execution, does the CLR and JIT compiler perform optimizations so that only the parts of that .dll that actually need to be used are loaded into memory, or is the whole .dll loaded/compiled into memory at once?

    You seem to be conflating two things: loading and compiling.

    The loading into memory is done normally using a Memory Mapped File, see this post for more on that. What this means is that at any point, only the pages that are actually needed are loaded, although Windows by default does do a certain amount of prefetching, and small DLLs would be fully loaded anyway.

    JIT compiling is a separate thing. The JITter is exactly that: a just in time compiler. Which means that each type and method is only compiled when it is needed, which I think is normally on compilation of the methods which call/use them. It is a recursive process, only types and methods which are needed by the one about to execute are compiled.

    how can the runtime know which methods it needs to call without first loading the whole .dll into memory?

    I would imagine the MethodDef/MethodRef tables in the DLL would always need to be fully loaded in order for the JITter to be able to search through them. This doesn't mean it needs to load the whole DLL though, just the headers. And every type and method within would refer back to those tables.

    How does this behavior differ if the .dll is managed code vs. unmanaged code?

    Unmanaged DLLs also use Memory Mapped files for loading, but in this case there is no JITter, the thread will just proceed in the usual fashion, causing page faults on any not-yet-loaded pages.

    How does this behavior differ if we use the "Produce Single File" publish setting in Visual Studio?

    I believe this will cause the assembly resolver to fully load each DLL into memory. But that doesn't mean it will happen immediately when you start the application, nor that JIT will happen early either.


    Long and short: unless your DLLs are particularly large you shouldn't have any issues with any of this.