I am trying to optimize my application for for it to perform well right after it is started. At the moment, its distribution contains 304 binaries (including external dependencies) totaling 57 megabytes. It is a WPF application doing mostly database access, without any significant calculations.
I discovered that the Debug configuration offers way better (~5 times gain) times for most operations, as they are performed for the first time during the lifetime of the application's process. For example, opening a specific screen within the app takes 0.3 seconds for NGENed Debug, 0.5 seconds for JITted Debug, 1.5 seconds for NGENed Release and 2.5 seconds for JITted Release.
I understand that the gap in JIT compilation time is caused by the JIT compiler applying more aggressive optimizations for the Release binaries. From what I can tell, Debug and Release configurations differ by the /p:DebugType
and /p:Optimize
switches passed to the C# compiler, but I see the same performance gap even if I build the application with /p:Configuration=Release /p:DebugType=full /p:Optimize=false
– that is, the same image debug options as in /p:Configuration=Debug
.
I confirm that the options were applied by looking at the DebuggableAttribute
applied to the resulting assembly. Observing the NGEN output, I see <debug>
added to the names of some assemblies being compiled – how does NGEN distinguish between debug and non-debug assemblies? The operation being tested uses dynamic code generation – what level of optimization is applied to dynamic code?
Note: I am using the 32-bit framework due to external dependencies. Should I expect different results on x64?
Note: I also do not use conditional compilation. So the compiled source is the same for both configurations.
If, as you say, you have 304 assemblies to be loaded, then this is likely a cause of your app running slow.
This seems like an extremely high number of assemblies to be loading.
Each time the CLR reaches code from another assembly that's not already loaded in the AppDomain, it has to load it from disk.
You might consider using ILMerge to merge some of those assemblies. This will reduce the delay in loading the assemblies from disk (you take one, larger, disk hit up-front).
It may require some experimentation, as not everything likes being merged (particularly those which use Reflection, and depend upon the assembly filename never changing). It may also result in very large assemblies.
Okay, a few questions here.
From what I can tell, Debug and Release configurations differ by the
/p:DebugType and /p:Optimize switches passed to the C# compiler, but I
see the same performance gap even if I build the application with
/p:Configuration=Release /p:DebugType=full /p:Optimize=false – that
is, the same image debug options as in /p:Configuration=Debug.
Although the tick boxes are the same, changing to Release mode also causes certain internal code paths to be removed, like Debug.Assert()
(used heavily in the Microsoft internal code). So these are not evaluated at runtime, which causes some performance improvement. DebugType=full
generates a PDB matching the code it was compiled against, so isn't a performance hit in itself. If the PDB is deployed, the exception handling code will use the PDB to provide more useful stack traces against the compiled code. Release mode also internally triggers some memory improvements, because Debug versions are used to attach the debugger.
NGEN is a tool is used to 'potentially' optimise an application. It optimises the code to work on specific to the computer you are on. But it can have drawbacks, as the JIT compiler can make changes to the layout of the code in memory, whereas NGEN is more static in its nature.
As for 32-bit (x86) dependencies, your app will now run in x86 mode. If the dependency had both x86 and x64 version available, and if your app is compiled into under an 'Any CPU' compilation mode, the JIT compiler will switch automatically between the 2. NGEN would only generate a specific version for the current computer. So if you did NGEN and then distribute, it would only work for the specific architecture you compiled against.
If you are not utilising conditional compilation features, it does not really matter if you switch from Debug to Release. But you will see a performance benefit in Release.
With the NGEN, I suggest you test extensively to see the benefits over the 2. It doesn't always result in better performance.
Are you running it under the debugger ('F5') or without the debugger ('ctrl+F5')? If the former, ensure Tools -> Options -> Debugging -> "Suppress JIT optimization on module load" is unchecked