15

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.

BoltClock
  • 630,065
  • 150
  • 1,295
  • 1,284
cynic
  • 5,005
  • 1
  • 22
  • 40
  • 1
    As your Release NGENed assemblies still are slower than Debug, are you sure that JIT is the problem ? You could try a profiler ... Also, check that you aren't using #if DEBUG in your code. – Guillaume Apr 16 '12 at 10:02
  • 1
    Are you using XmlSerializer without SGEN ? http://stackoverflow.com/questions/771727/net-release-build-working-slower-than-debug – Guillaume Apr 16 '12 at 10:07
  • I am not using `#if DEBUG` (edited question to reflect this). The application is not necessarily slower on Release - it may even be faster, but I am measuring cold startup time, not throughput. I suspect the JITting of dynamic methods and so I ask what decides the optimization level of those. – cynic Apr 16 '12 at 10:08
  • This all makes little sense. These are *warm* start numbers, ngen makes cold starts slower. I guess you ought to try using the [Debuggable] attribute explicitly. – Hans Passant Apr 16 '12 at 10:39
  • @HansPassant - what do you mean? The times I've given are measured as follows: after the application loads and welcome screen ("launchpad") appears, a tile is clicked, and time is counted until the requested screen appears. That's "cold" startup for me. "Warm" is when I click "Back" (the app is navigation-based) and click the same tile again (no "caching" of screens is being done by my code). The "warm" performance is satisfactory in all configurations. – cynic Apr 16 '12 at 10:46
  • No, cold start is when you run the app for the very first time after a boot. Dominated by finding the files on disk. Ngen makes it worse because it doubles the number of files. Which is why Microsoft recommends to *not* ngen small assemblies. You are measuring warm start here. Yes, ngen speeds that up. No jitting and *no optimization*. – Hans Passant Apr 16 '12 at 10:55
  • @Guillaume - I SGEN'ed and NGEN'ed the sole assembly in the solution that contains types used with `XmlSerializer` and observed no changes in performance. – cynic Apr 16 '12 at 11:44
  • NGEN images are not used always used (when loading assemblies with Assembly.LoadFrom for instance) it might be the case for some of your assemblies. – Guillaume Apr 16 '12 at 12:27
  • @Guillaume - unfortunately not. All assemblies are implicitly loaded in the Load context from the entry assembly. – cynic Apr 16 '12 at 12:33
  • Try opening your project file in notepad or a similar text editor and seeing if there's another difference in the compile settings that you're overlooking (usually it's one that is hidden from the screens). – Seph May 07 '12 at 05:47

3 Answers3

2

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.

  • I'll try, but I think that's unlikely the cause of the speed difference between JITted Debug and NGENed Release configurations. I run the tests on a SSD machine. – cynic May 07 '12 at 10:22
1

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.

Dominic Zukiewicz
  • 7,704
  • 7
  • 37
  • 57
  • I do believe that calls to `ConditionalAttribute`d methods are stripped at the stage of compilation to IL, not JIT. Regardless, the difference in my case is not between JIT/NGEN but Debug/Release. – cynic Apr 21 '12 at 16:51
  • So are you asking what the difference is between Debug and Release if the configuration is the same? – Dominic Zukiewicz Apr 23 '12 at 11:22
  • Yes. Are there extra flags in PE (apart from `DebuggableAttribute`)? And most importantly, what level of optimization is applied to dynamically generated code? – cynic Apr 24 '12 at 05:50
  • I don't think there is? Release builds are just a pre-configured set of options to make your application run optimally. ASP.NET applications work differently as they are generated on the fly. – Dominic Zukiewicz Apr 25 '12 at 17:36
1

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

Mark Sowul
  • 9,386
  • 1
  • 39
  • 48