Some tips on .NET Memory usage

  • Always try to use string.Compare() method for testing string equality and inequality
  • use string.CompareOrdinal() method for case sensitive string comparisons
  • when returning string from a method call insure that you never return “null” return string.Empty instead
  • Always initialize strings to string.Empty
  • when instantiating StringBuilder use the overloaded constructor to specify the initial capacity
  • Prefer string.format when concatenating value types into strings
   1: int number = 4545;
   2: string x = string.Format("{0} is a even number", number);
   3: //above is better than using
   4: string y = number.ToString() + " is a even number";

  • Use BitArray instead of using Array of booleans
  • use for loop for iterating over arrays, use foreach for iterating over collections
  • Set initial capacity for StringBuilder, SortedList, HashTable, ArrayList, Queue, Stack etc
  • First add all elements to HashTable and then create Sortedlist from this HashTable, since adding to SortedList directly is very expensive
  • Use DictionaryEntry to iterate over Hastable entries
  • Garbage Collector settings: In .Net there are 2 versions of the garbage collector, Workstation Garbage collector (used on single processor systems) and Server garbage collector (used on multi processor systems. Server garbage collector pauses app during GC and uses 1 thread and 1 managed heap for each CPU. The workstation garbage collector minimizes the pauses since it runs the GC concurrently with the app’s worker threads.
  • On single CPU machine, Workstation GC and Server GC behave the same way, but you can prevent the GC from running concurrently with the App threads by using this config in app.config or machine.config:
   1: <configuration>
   2:     <runtime>
   3:         <gcConcurrent enabled="false"/>
   4:     </runtime>
   5: </configuration>

  • By default, non-interactive apps like Windows Services, .NET Remoting apps use Workstation GC. Force such apps (running on multi core CPU machines) to use the server GC by adding the following to app.config:
   1: <configuration>
   2:     <runtime>
   3:         <gcServer enabled="true"/>
   4:     </runtime>
   5: </configuration>

  • WekReference: Use WeaKReference to create cache versions of your objects. weak references are references to objects that last until the point that GC does not collect them, once GC runs, then these objects need to be repopulated from original source. Here is an example of cached File:
   1: public class CachedTextFile
   2: {
   3:     public readonly string FileName;
   4:     WeakReference wrText;
   5:     public CachedTextFile(string filename)
   6:     {
   7:         this.FileName = filename;
   8:     }
   9:  
  10:     private string ReadFile()
  11:     {
  12:         string text = string.Empty;
  13:         using (StreamReader sr = new StreamReader(FileName))
  14:         {
  15:             text = sr.ReadToEnd();
  16:         }
  17:         wrText = new WeakReference(text);
  18:         return text;
  19:     }
  20:     
  21:     public string GetText()
  22:     {
  23:         object text = null;
  24:         //check weak reference
  25:         if (wrText != null)
  26:             text = wrText.Target;
  27:         if (text != null)
  28:         {
  29:             //string still in cache
  30:             return text.ToString();
  31:         }
  32:         else
  33:         {
  34:             //read from disk
  35:             return ReadFile();
  36:         }
  37:     }
  38: }
  39:  
  40:  
  41:         CachedTextFile cacheFile = new CachedTextFile(@"C:\temp\test.txt");
  42:        // this is the first call hence gets the text from disk, instead of memory/cache
  43:         Console.WriteLine(cacheFile.GetText());
  44:         //force garbage collection manually
  45:         GC.Collect();
  46:         GC.WaitForPendingFinalizers();
  47:         //following causes the app to read the file again
  48:         Console.WriteLine(cacheFile.GetText());
  • Dont create/destroy large objects (objects with size greater than 85000bytes) frequently, since such objects get allocated on the Large Object Heap which isn’t freed by the GC. Consider splitting large objects into 2 or more smaller objects like Partitioned Array’s.

Dispose and Finalize methods

Here are are few tips on using the IDisposable pattern and using Finalizers

  • Implement IDiposable if accessing managed resources like SQLConnection/Command objects and not disposing them from within the method which instantiates the managed resource
  • Implement Finalizer only if using unmanaged resources (Files, Streams, Windows Handles, Pinvoke) and not disposing them from with the method that instantiates the unmanaged resources
  • Insure that each Type that has a finalizer is responsible for disposing only 1 unmanaged resource, and never reference “reference types” from within a Finalizer
  • Use a protected “disposed” bool within a IDiposable type and check its value in the beginning of each method of that type
  • If a type has a “Open” method which opens a managed resource, then insure that you Implement a Close() method which calls the explicit implementation of IDisposable.Dispose() method
   1: public class EncryptedStream : IDisposable
   2: {
   3:     public void Close()
   4:     {
   5:         (this as IDisposable).Dispose();
   6:     }
   7:  
   8:     void IDisposable.Dispose()
   9:     {
  10:        //Release resources
  11:     }
  12: }

  • If the Dispose() method can be called from multiple threads then insure that you use lock(this) before releasing resources. Here is a correct implementation of IDisposable and Finalize pattern which has unmanaged and managed resources to release:
   1: public class DisposeType : IDisposable
   2: {
   3:     protected bool disposed;
   4:     //insure the access modifier is protected so that derived classes can also make use of the same    
   5:     protected virtual void Dispose(bool disposing)
   6:     {
   7:         if (disposed)
   8:             return;
   9:  
  10:         lock (this)
  11:         {
  12:             if (disposing)
  13:             {
  14:                 // Release Managed resources here
  15:             }
  16:             // Release UnManaged resources here
  17:             //call the base object's Dispose protected method
  18:             base.Dispose(disposing);
  19:             disposed = true;
  20:         }
  21:     }
  22:  
  23:     public void Dispose()
  24:     {
  25:         Dispose(true);
  26:         GC.SuppressFinalize(this);
  27:     }
  28:  
  29:     ~DisposeType()
  30:     {
  31:         Dispose(false);
  32:     }
  33: }
  • Compound Finalizable objects: If you have a finalizable object that occupies lot of memory then consider splitting it into 2 classes, one that contains the finalizable object and other that contains the high memory using variables:
   1: public class CompoundObject : IDisposable
   2: {
   3:     //this array takes lot of memory
   4:     int[] array;
   5:     //instance of inner finalizable object
   6:     ClipBoardWrapper cwrapper;
   7:  
   8:     public CompoundObject(int hwnd, int elements)
   9:     {
  10:         array = new int[elements];
  11:         cwrapper = new ClipBoardWrapper(hwnd);
  12:     }
  13:  
  14:     public void Dispose()
  15:     {
  16:         cwrapper.Dispose();
  17:     }
  18:  
  19:     private class ClipBoardWrapper : IDisposable
  20:     {
  21:         [System.Runtime.InteropServices.DllImport("user32")]
  22:         private static extern int OpenClipboard(int hwnd);
  23:  
  24:         [System.Runtime.InteropServices.DllImport("user32")]
  25:         private static extern int CloseClipboard();
  26:         
  27:         public ClipBoardWrapper(int hwnd)
  28:         {
  29:             OpenClipboard(hwnd);
  30:         }
  31:         
  32:         public void Dispose()
  33:         {
  34:             CloseClipboard();
  35:             GC.SuppressFinalize(this);
  36:         }
  37:         ~ClipBoardWrapper()
  38:         {
  39:             Dispose();
  40:         }
  41:  
  42:     }
  43: }
Twitter