Wednesday, March 28, 2007

.Net Memory Optimization

Forcing Garbage Collection in .NET

There might be times in your application when you want to force the .NET Garbage Collector (GC) to spin through all unused objects and de-allocate them. The method for accomplishing this task is the GC.Collect method. When you call GC.Collect, the GC will run each object's finalizer on a separate thread. Therefore, another method to keep in mind is GC.WaitForPendingFinalizers. This synchronous method that will not return until the GC.Collect has finished its work.

Here's a simple example of using these two methods:

using System;
namespace GCCollect
{
  class Account
{
public Account(string accountNumber)
{
this.accountNumber = accountNumber;
Console.WriteLine("Account::Acount - c'tor");
}
    ~Account()
{
Console.WriteLine("Account::~Acount - d'tor");
}
    protected string accountNumber;
    override public string ToString() { return accountNumber; }
  };
 
  class Class1
{
    [STAThread]
static void Main(string[] args)
{
      CreateAccount("111006116");
      GC.Collect();
      GC.WaitForPendingFinalizers();
      Console.WriteLine("Application ending");
    }
 
    public static void CreateAccount(string accountNumber)
{
      Console.WriteLine("CreateAccount - instantiate Account object");
      Account account = new Account(accountNumber);
      Console.WriteLine("CreateAccount - created account number {0}",
                        account);
    }
  }
}

Type Sizing

Consider the following C# structure (for simplicity, I have avoided specifying any access modifier for these members):

struct BadValueType
{

char c1;

int i;

char c2;
}

As with the default packing in unmanaged C++, integers are laid out on four-byte boundaries, so while the first character uses two bytes (a char in managed code is a Unicode character, thus occupying two bytes), the integer moves up to the next 4-byte boundary, and the second character uses the subsequent 2 bytes. The resulting structure is 12 bytes when measured with Marshal.SizeOf (it's also 12 bytes when measured with sizeof on the .NET Framework 2.0 running on my 32-bit machine). If I reorganize this as follows, the alignment works in my favor, resulting in an 8-byte structure:

struct GoodValueType
{

int i;

char c1;

char c2;

}

Another noteworthy point is that smaller types use less memory. That may seem obvious, but many projects use standard integers or decimal values even when they are unnecessary. In my GoodValueType example, assuming the integer values will never be greater than 32767 or less than -32768, I can cut the size of this type even further by using a short integer, as shown in the following:

struct GoodValueType2
{
    short i;
    char c1;
    char c2;
}

Properly aligning and sizing this type reduced it from 12 to 6 bytes. (Marshal.SizeOf will report 4 bytes for GoodValueType2, but that's because the default marshaling for a char is as a 1 byte value.) You will be surprised how much the size of your structures and classes can be reduced if you pay attention.

ENum

enum AddressType
{

Home,

Secondary,

Office

}

class Address
{

public bool IsPayTo;

public AddressType

AddressType;

public string Address1;

public string Address2;

public string City;

public string State;

public string Zip;

}


Figure 3 Reducing Type Size

enum AddressType : byte

{

Home,

Secondary,

Office

}

class Address

{

byte _isPayTo;

AddressType _addrType;

string _address1;

string _address2;

string _city;

string _state;

string _zip;

}


Singletons

public class Singleton
{
    private static Singleton _instance = new Singleton();
 
    public static Singleton GetInstance()
    {
        return _instance;
    }
}

The singleton pattern ensures that only a single instance of a class is normally used by an application, but still allows alternate instances to be created as required. This saves memory because the application can use the one shared instance, rather than having different components allocate their own private instances. Use of the static constructor ensures that memory for the shared instance is not allocated until some portion of the application requires it. This might be important in large applications that support many different types of functionality, since the memory for the object is only allocated if the class is actually used.


Use String.Builder

The String class provides a number of opportunities to consume large amounts of memory unintentionally. The simplest example is the concatenation of strings. Concatenating four strings incrementally (adding one string at a time to the new string) will internally produce seven string objects, since each addition produces a new string. The StringBuilder class in the System.Text namespace joins strings together without allocating a new string instance each time; this efficiency greatly improves memory utilization. The C# compiler also helps in this regard because it transforms a series of string concatenations in the same code statement into a single call to String.Concat.


String Replace

The String.Replace method provides another example. Consider a system that reads and processes a number of input files sent from an external source. These files might require preprocessing to put them into an appropriate format. For discussion purposes, suppose I had a system that had to replace each occurrence of the word "nation" with "country", and each occurrence of the word "liberty" with "freedom". This can be done quite easily with the following code snippet:

//Bad Logic

using(StreamReader sr = new StreamReader(inPath))
{
    string contents = sr.ReadToEnd();
    string result = contents.Replace("nation", "country");
    result = result.Replace("liberty", "freedom");
 
    using(StreamWriter sw = new StreamWriter(outPath))
    {
        sw.Write(result)
    }
}
  This works perfectly, at the expense of creating three strings that are the length of the file. The Gettysburg Address is roughly 2400 bytes of Unicode text. The U.S. Constitution is over 50,000 bytes of Unicode text. You see where this is going.

Now suppose each file was roughly 1MB of string data and I had to process up to 10 files concurrently. Reading and processing these 10 files will consume, in our simple example, around 10MB of string data. This is a rather large amount of memory for the garbage collector to allocate and clean up on an ongoing basis.

//Good Logic

static void ProcessFile(FileStream fin, FileStream fout)
{
    int next;
    while ((next = fin.ReadByte()) != -1)
    {
        byte b = (byte)next;
        if (b == 'n' || b == 'N')
          CheckForWordAndWrite(fin, fout, "nation", "country", b);
        else if (b == 'l' || b == 'L')
          CheckForWordAndWrite(fin, fout, "liberty", "freedom", b);
        else
          fout.WriteByte(b);
    }
}
 
static void CheckForWordAndWrite(Stream si, Stream so,
    string word, string replace, byte first)
{
    int len = word.Length;
 
    long pos = si.Position;
    byte[] buf = new byte[len];
    buf[0] = first;
    si.Read(buf, 1, word.Length-1);
 
    string data = Encoding.ASCII.GetString(buf);
    if (String.Compare(word, data, true) == 0)
        so.Write(Encoding.ASCII.GetBytes(replace), 0, replace.Length);
    else
    {
        si.Position = pos;    // reset stream
        so.WriteByte(first);  // write orig byte
    }
}
Performance Monitoring
 
        Performance too is already present in .Net will find By

Tools >> Performance Tools

The Windows Performance Monitor does not solve performance problems of testing, gathering, analyzing, and modifying a system applies equally well to all aspects of performance, including memory utilization.

3 comments:

Anonymous said...

Please do mention on which version or release the code works as some code might not work in older versions....

Mahesh said...
This comment has been removed by the author.
Mahesh said...

Above Article code work for both frame work 1.0 and 2.0 but the Memory allocation is different