views:

175

answers:

1

When running a .NET 2.0 WinForms app in a Terminal Services environment, I'm seeing some unexpected results that I can't quite explain. Everything I have read has indicated that JIT'ed assemblies (i.e., not using NGen to create native images) result in all code space being stored in private pages, increasing working set size / memory pressure. However, actual results (verified using Process Explorer, VMMap, and WinDbg) show that even the JIT'ed assemblies are indeed being placed in shareable pages (and are indeed being shared when there are multiple instances of the app running, even under separate TS sessions/users).

Can anyone explain why this might be? This is running in a W2K8 Server environment, so ASLR explains why a lack of specific base addresses for each assembly & the resulting rebasing isn't causing problems. Still, it seems the fact these aren't native PE images should result in the code for those assemblies being stored in private pages.

This was discovered when we started investigating using NGen to cut down on memory pressure, but actually found it increased working set size - since the JIT'ed assemblies were already being shared.

The most recent reference I've found is here, which again differs from our actual findings:

http://blogs.msdn.com/morgan/archive/2009/03/07/developing-net-applications-for-deployment-on-terminal-services-or-citrix.aspx

Edit: I should add that since first posting the question, more experiments on Windows Server 2003 test boxes are also apparently showing the JIT'ed assemblies being sharable between processes. I'm still stumped as to why all advice I can find indicates NGen is required, but all the real-world evidence contradicts that. I'm really hoping the experts here can shed some light.

Thanks!

Edit: I've dusted off all my .NET / CLR books and am running out of ideas for search queries to try to solve this one; who's going to make my day by helping eliminate that awful nagging feeling of "I don't understand what's going on"!?! :)

+2  A: 

Hi, I think you are looking directly at module pages. When you JIT code, it won't show up under your DLL - it shows up in memory the runtime allocates. The module pages you are looking at are mostly metadata and IL, which is why they are still sharable.

As an experiment, I wrote a small program that generates 30K static methods and calls them. On my system, the JIT version of this program has 8.2 MB of private comitted memory, while the NGEN version has 3.8.

Even within your module pages, however, NGEN does help with memory use. When the runtime is able to load an NGEN image, it doesn't have to read the metadata of your module in order to JIT the code. The JIT version of my test application is using 2.3MB of working set. The NGEN version is using 32 kilobytes.

NGEN should also help your startup time. Impact to warm startup time can be negligable, but the impact to cold startup time (saving reading all those pages off the disk) can be noticable.

Bill Wert
Thanks for the response! I'll break things down and do some simpler tests, but I haven't seen the same results. And from what I've read (and the blog posting I referenced), I really should see the difference in module pages.AFAIK, the CLR still needs to read the metadata to support reflection, CAS policy checks, etc; NGEN doesn't eliminate that.Since this is a TS server-based app run by many users, memory usage is critical, while cold start time is essentially irrelevant.
allgeek
No problem! The metadata accesses for Reflection and the like are all lazy - nothing is loaded eagerly to support those scenarios. I understand that in a TS scenario, memory usage is the key factor. I encourage you to read this article: http://msdn.microsoft.com/en-us/magazine/cc163610.aspx, if you haven't. When you re-look at your module pages, take a look at the WS column in vmmap, instead of size/commited. WS is the real in use page size, which is where I saw 2.3MB to 32KB. Good luck!
Bill Wert