views:

1995

answers:

7

I was wondering if anyone had any tips to reduce the memory usage of .NET applications. Consider the following simple C# program:

class Program
{
    static void Main(string[] args)
    {
        Console.ReadLine();
    }
}

Compiled in release mode for x64 and running outside visual studio, the task manager reports the following:

Working Set:          9364k
Private Working Set:  2500k
Commit Size:         17480k

It's a little better if it's compiled just for x86:

Working Set:          5888k
Private Working Set:  1280k
Commit Size:          7012k

I then tried the following program, which does the same but tries to trim process size after runtime initialization:

class Program
{
    static void Main(string[] args)
    {
        minimizeMemory();
        Console.ReadLine();
    }

    private static void minimizeMemory()
    {
        GC.Collect(GC.MaxGeneration);
        GC.WaitForPendingFinalizers();
        SetProcessWorkingSetSize(Process.GetCurrentProcess().Handle,
            (UIntPtr) 0xFFFFFFFF, (UIntPtr)0xFFFFFFFF);
    }

    [DllImport("kernel32.dll")]
    [return: MarshalAs(UnmanagedType.Bool)]
    private static extern bool SetProcessWorkingSetSize(IntPtr process,
        UIntPtr minimumWorkingSetSize, UIntPtr maximumWorkingSetSize);
}

The results on x86 Release outside Visual Studio:

Working Set:          2300k
Private Working Set:   964k
Commit Size:          8408k

Which is a little better, but still seems excessive for such a simple program. Does anyone know any tricks to make a C# process a bit leaner? I'm writing a program that's designed to run in the background most of the time. I'm already doing any user interface stuff in a separate application domain which means the user interface stuff can be safely unloaded, but taking up 10 MB when it's just sitting in the background seems excessive.

P.S. As to why I would care --- (Power) users tend to worry about these things. Even if it has nearly no effect on performance, semi-tech-savvy users (my target audience) tend to go into hissy fits about background application memory usage. Even I freak when I see Adobe Updater taking 11 MB of memory and feel soothed by the calming touch of Foobar2000, which can take under 6 MB even when playing. I know in modern operating systems, this stuff really doesn't matter that much technically, but that doesn't mean it doesn't have an affect on perception.

+3  A: 

No specific suggestions per se, but you might take a look at the CLR Profiler (free download from Microsoft).
Once you've installed it, take a look at this how-to page.

From the how-to:

This How To shows you how to use the CLR Profiler tool to investigate your application's memory allocation profile. You can use CLR Profiler to identify code that causes memory problems, such as memory leaks and excessive or inefficient garbage collection.

Donut
+17  A: 

.NET applications will have a bigger footprint compared to native applications due to the fact that they both have to load the runtime and the application in the process. If you want something really tidy, .NET may not be the best option.

However, keep in mind that if you application is mostly sleeping, the necessary memory pages will be swapped out of memory and thus not really be that much of a burden on the system at large most of the time.

If you want to keep the footprint small, you will have to think about memory usage. Here are a couple of ideas:

  • Reduce the number of objects and make sure not to hold on to any instance longer than required.
  • Be aware of List<T> and similar types that double capacity when needed as they may lead to up 50% waste.
  • You could consider using value types over reference types to force more memory on the stack, but keep in mind that the default stack space is just 1 MB.
  • Avoid objects of more than 85000 bytes, as they will go to LOH which is not compacted and thus may easily get fragmented.

That is probably not an exhaustive list by any means, but just a couple of ideas.

Brian Rasmussen
IOW, the same sorts of techniques that work to reduce native code size work in .NET?
Robert Fraser
I guess there is some overlap, but with native code you have more choices when it comes to use of memory.
Brian Rasmussen
+10  A: 

One thing you need to consider in this case is the memory cost of the CLR. The CLR is loaded for every .Net process and hence factors into the memory considerations. For such a simple / small program the cost of the CLR is going to dominate your memory footprint.

It would be much more instructive to construct a real application and view the cost of that compared to the cost of this baseline program.

JaredPar
+1 for the suggestion of constructing a real application!
RichardOD
+5  A: 

Might want to look at the memory usage of a "real" application.

Similar to Java there is some fixed amount of overhead for the runtime regardless of the program size, but memory consumption will be much more reasonable after that point.

Eric Petroelje
+12  A: 
  1. You might want to check out this SO question.
  2. Working set != actual memory footprint. This link (on an MSDN blog) is all about demystifying working set, process memory and how to perform accurate calculation's on your total in-RAM consumption.

I will not say that you should ignore the memory footprint of your app -- obviously, smaller and more efficient does tend to be desirable. However, you should consider what your actual needs are.

If you are writing a standard WinForms/WPF client app which is destined to run on an individual's PC, and is likely to be the primary app in which the user operates, you can get away with being more lackadaisical about memory allocation. (So long as it all gets deallocated.)

However, to address some folks here who say not to worry about it: If you're writing a WinForms app which will be running in a terminal services environment, on a shared server possibly utilized by 10, 20 or more users, then yes, you absolutely must consider memory usage. And you will need to be vigilant. The best way to address this is with good data structure design and by following best practices regarding when and what you allocate.

John Rudy
A: 

Addressing the general question in the title and not the specific question:

If you are using a COM component that returns a lot of data (say large 2xN arrays of doubles) and only a small fraction is needed then one can write a wrapper COM component that hides the memory from .NET and returning only the data that is needed.

That is what I did in my main application and it significantly improved the memory consumption.

Peter Mortensen
+1  A: 

There are many ways to reduce your footprint.

One thing you'll always have to live with in .Net is that the size of the native image of your IL code is huge

And this code cannot be completely shared between application instances. Even ngen'ed assemblies are not completely static, they have still some little parts that need JITting.

People also tend to write code that blocks memory far longer than necessary.

Often seen example: Taking a Datareader, loading the contents into a DataTable just to write it into an XML. You can easily run into an OutOfMemoryException. OTOH, you could use an XmlTextWriter and scroll through the Datareader, emitting XmlNodes as you scroll through the database cursor. That way, you only have the current DB record and its XML output in memory. Which will never (or is unlikely to) get a higher GC generation and thus can be reused.

The same applies to getting a list of some instances, doing some stuff (that spawns of thousands of new instances, which might stay referenced somewhere), and even though you don't need them afterwards, you still reference everything until after the foreach. Explicitly null-ing your input list and your temporary by-products means, this memory can be reused even before you exit your loop.

C# has an excellent feature called iterators. They allow you to stream objects by scrolling through your input and only keep the current instance until you get the next one. Even by using LINQ, you still don't need to keep all of it around just because you wanted it to be filtered.

Robert Giesecke