C# Memory

Is C# memory-efficient? We improve the memory usage in a program by modifying the storage of large collections. By using CLRProfiler, we gauge memory. We test the memory usage of classes and value types.

Screenshot of using CLRProfiler
Tools used:

CLRProfiler, 32-bit Windows

Example

Two

The program here has two classes that contain object references. The first class TestArray1 contains only three object references, for three arrays. The array variables such as string[] are actually only references and are lightweight.

Class

The second class TestArray2 contains an array of classes. Each class in the array has the same three value types. The program tests three separate arrays of objects against an array of classes with those same values.

C# program that measures memory

using System;

class TestArray1
{
    public string[] _value1;   // Array of string references
    public int[] _value2;      // Array of integers
    public DateTime[] _value3; // Array of structs
}

class TestArray2
{
    public TestClass1[] _class1; // Array of classes
}

class TestClass1
{
    public string _value1;     // String reference
    public int _value2;        // Integer
    public DateTime _value3;   // Struct
}

class Program
{
    const int _constant = 10000000; // <-- Number of elements required

    static void Main()
    {
	if (true) // <-- True will test the arrays, false will check the classes
	{
	    //
	    // This tests the memory usage with separate arrays.
	    //
	    TestArray1 test1 = new TestArray1();
	    test1._value1 = new string[_constant]; // <-- Allocate each array
	    test1._value2 = new int[_constant];
	    test1._value3 = new DateTime[_constant];
	}
	else
	{
	    //
	    // This tests the memory usage of array of classes.
	    //
	    TestArray2 test2 = new TestArray2();
	    test2._class1 = new TestClass1[_constant];
	    for (int i = 0; i < test2._class1.Length; i++)
	    {
		test2._class1[i] = new TestClass1(); // <-- Allocate each class
	    }
	}
    }
}
Value

Value types are stored in each class. The string reference actually stores no data unless assigned. The integer value contains storage for the value inline. And the DateTime struct also contains its storage inline.

StringsIntDateTime

The Main method contains two paths, which you can change by turning the "true" Boolean literal to "false." In the first path, the three arrays in TestArray1 are allocated with 10 million values each.

True, False

And:In the second path, the TestArray2 class is allocated, and its array of TestClass1 references is allocated.

Finally:The class references are allocated. In the end, both paths allocate the same values.

CLRProfiler

Net

The CLRProfiler application is developed by Microsoft. It provides an accurate look into your application's memory usage, although it does affect performance. When you download the CLRProfiler, extract it to your "C:\" directory.

Then:Browse to the x86 folder, and double-click on CLRProfiler.exe. The program will start.

The profiler dialog box will appear at this point. Click on the "Start Application" button and then select the Release binary for your program (such as ConsoleApplication461.exe) that is in the Visual Studio Projects directory.

Start

Your application will execute. If the program is too large, it will be slow. You may need to reduce its allocations first. Next, you will see the Summary dialog. Click on the buttons and explore the graphs and histograms.

CLR Profiler: MSDN

Tip:I usually just take screenshots of the charts instead of saving the files, because I have organization problems.

Results

Here we look again at the example program and inspect the two snapshots from CLRProfiler I took when executing the programs in CLRProfiler. First, here is the benchmark for TestArray1 (which is in the first path of the Main entry point).

Measurements

Total:             153 MB
DateTime array:     76 MB
Int array:          38 MB
String references:   8 MB
Memory usage of arrays

Next, we look at the memory usage from running the program in the second path in the Main entry point, which tests TestArray2. The memory usage here is much higher. The values and references are not allocated in contiguous memory.

Info:Memory usage is 267 MB total. This is 229 MB for all class object data and 38 MB for all class references in the array.

Memory usage of classes

By using separate arrays, which contain all their values and references to objects in continuous storage, the program saves over 100 MB. If you look at Task Manager when running these Release executables, the difference is greater.

And:I am not certain why this occurs, but it also proves the massive memory efficiency gain.

Task manager measurement

Program with three arrays:  40612 KB [smaller]
Program with one array:    276936 KB

OOP

Object-oriented programming

I consider object-oriented programming's greatest strength to be its separation of concerns. The class type itself is not the key to OOP, but rather the barricading of outside accesses to discrete units of data and behavior.

Therefore:If you change a class to use three arrays instead of a class array, you do not violate the principles of OOP.

Object-Oriented ProgrammingCover logo

Factory design pattern. If your program uses an object such as Employee that is stored in an array, you can store the data in separate arrays and return a new object of type Employee that is constructed lazily.

But:This is only useful when only one Employee object will be needed by the caller. And it could lead to more problems.

Factory Pattern

Summary

The C# programming language

You can greatly decrease the memory usage of your C# programs by using arrays of value types instead of separate class instances. This allows more efficient data storage by the CLR.

Note:The program here with 10 million simple instances of three values was reduced by 114 MB or more.


C#: .NET