h1

What is a Memory Leak in .net World?

January 16, 2009
What is a Memory Leak?
In Computer Science, “Memory Leak” is a particular type of unintentional memory consumption by a computer program which occurs when the program fails to release memory that is no longer needed. Usually, this condition arises due to a bug in the program which prevents it from freeing up memory that it no longer needs.

Does traditional memory leak exist in .Net?
.Net CLR does an excellent job of reclaiming the memory that it has assigned to a program on the request. So, in theory if a memory has been assigned by .Net CLR, it can reclaim this memory by cleaning up our live references. However, we can have a native leak in the following cases:
• Improper use of Interops
• Unsafe code
• GCHandles
• Failure to dispose the objects that internally use native resources

There are many more reasons, but those are not so common.

Memory Management by .Net CLR
Though .Net CLR manages the memory in an excellent way, it does not take into account any unintentional leak done by a programmer. The “unintentional leak” here means that the programmer fails to remove object references for an object instance that should not be alive anymore.

Code Snippet
Let us write a simple application where supposedly the programmer wanted to put some data in a hashtable data structure and remove the item from hashtable when the task for that item was over. Apparently, the programmer forgot to write the code to remove items from one of the code flow paths.

public partial class Form1 : Form
{
private Hashtable hshDataHolder = new Hashtable();
public Form1()
{
InitializeComponent();
}

private void btnLeak_Click(object sender, EventArgs e)
{
for (int i=0; i<50;i++)
{
byte[] _data = new byte[1024*1024];
hshDataHolder.Add(DateTime.Now.Ticks + i,_data);
Thread.Sleep(1);
}
}
}

In this program, things will work fine for a while, as our programmer friend wrote correct business logic to process the data stored in hashtable. However, over a period of time, the memory (specifically, the Virtual memory) consumption by the application shoots up as the data items that should have been removed start piling up.

Detecting a Managed Memory Leak
Code Review
One of the best practices in programming is to have a thorough code review. To an experienced eye, detecting a memory leak is easy if the code footprint is small.

WinDbg tool
However, in the case where lines of code run into tens of thousands, it is next to impossible to detect memory leak just by manual review process. This is especially the case if you are not the author of the program 

Using WinDbg, a code debugging tool, we are better equipped to find a memory leak.

WinDbg can be downloaded from Microsoft web site at: http://www.microsoft.com/whdc/devtools/debugging/installx86.Mspx.

The tool needs symbols files for the operating system, which can be downloaded at: http://www.microsoft.com/whdc/DevTools/Debugging/symbolpkg.mspx

Here are the steps to use WinDbg:
1. Run the application to be debugged for memory leak.

2. Make sure the application has reached a stage where it has consumed more memory than normally expected (hence, suffering from a ‘memory leak’).

3. Open WinDbg and attach the debugger to the application.

4. In the command box, type
.loadby sos mscorwks and press Enter. This loads the managed debugging extension SOS (Son Of Strike). This extension provides useful statistics about the managed code.

5. Run the command
!DumpHeap –stat
This command provides memory usage of each class type in the application. The output reads something like this:

————————————————————————————–
MT Count Size Type
————————————————————————————–
0b223718 18 720 System.Windows.Forms.Internal.IntNativeMethods+LOGFONT
7ae3c59c 17 748 System.Drawing.BufferedGraphics
79332f40 27 1512 System.Collections.Hashtable
7932b16c 68 1632 System.Collections.Stack
79331a6c 84 1680 System.RuntimeType
7933151c 17 1700 System.Char[]
79330508 143 1716 System.Object
79332a88 20 2032 System.Int32[]
79332178 150 2400 System.Int64
7b22394c 68 4896 System.Windows.Forms.Internal.DeviceContext
648c8c4c 95 5320 System.Configuration.FactoryRecord
7933303c 27 10200 System.Collections.Hashtable+bucket[]
793040bc 120 37832 System.Object[]
793308ec 832 69552 System.String
0015c5c8 675 181748 Free
7933335c 153 157299228 System.Byte[]
Total 3217 objects

6. We see that about 153 MB of memory is taken up by System.Byte[]! That’s a good starting point for our memory leak detection process. Next, let us inspect exactly at which point this memory is consumed by the application.

7. Run
!DumpHeap -type System.Byte[]
This command displays all the instances of System.Byte[] created by the application, along with their memory pointers and size of the objects.

————————————————————————————–
Address MT Size
————————————————————————————–
0b391110 7933335c 1048592
0b491130 7933335c 1048592
0b591150 7933335c 1048592
0b691170 7933335c 1048592
0b791190 7933335c 1048592
0b8911b0 7933335c 1048592
0ba91000 7933335c 1048592
0bb91020 7933335c 1048592
0bc91040 7933335c 1048592
0bd91060 7933335c 1048592
0be91080 7933335c 1048592
0bf910a0 7933335c 1048592
0c0910c0 7933335c 1048592
0c1910e0 7933335c 1048592
0c291100 7933335c 1048592
0c391120 7933335c 1048592
0c491140 7933335c 1048592
0c591160 7933335c 1048592
0c691180 7933335c 1048592
0c7911a0 7933335c 1048592
0c8911c0 7933335c 1048592
total 153 objects
Statistics:
MT Count TotalSize Class Name
7933335c 153 157299228 System.Byte[]
Total 153 objects

8. We observe that many byte[] of size about 1 MB are stored somewhere by the application. Let us check if they are live (referenced) instances, and if they are, where they are referenced.

9. Run
!GCRoot 0bc91040
Here 0bc91040 is the memory address of any one of the byte[] instance that we found in the previous step. Display shows an output similar to the following:

0:004> !GCRoot 0bc91040
Note: Roots found on stacks may be false positives. Run “!help gcroot” for
more info.
eax:Root:0128a4ec(WindowsApplication2.Form1)->
0128a638(System.Collections.Hashtable)->
012c1bd4(System.Collections.Hashtable+bucket[])
Scan Thread 0 OSTHread c6c
Scan Thread 2 OSTHread 8fc

10. Here we can see that the byte[] is referenced (live) and it is stored in a hashtable. (Think this could have been found without looking into the code?). The hashtable is created in Form1 class. This limits our scope further, as now we know that we have to look for a hashtable which is declared in Form1 class which stores a byte[].

11. Let us dump the object Form1 (that stores the hashtable in question) by the command
!do 0128a4ec
Here 0128a4ec is the address of Form1

Display shows output similar to the following:

————————————————————————————–
MT Field Offset Type VT Attr Value Name
————————————————————————————–
79332b38 4001e82 fe0 System.Int32 1 static 58 PropMainMenuStrip
79332b38 4001e83 fe4 System.Int32 1 static 59 PropMdiWindowListStrip
79332b38 4001e84 fe8 System.Int32 1 static 60 PropMdiControlStrip
79332b38 4001e85 fec System.Int32 1 static 61 PropSecurityTip
79332b38 4001e86 ff0 System.Int32 1 static 62 PropOpacity
79332b38 4001e87 ff4 System.Int32 1 static 63 PropTransparencyKey
79330508 4001e9a bc0 System.Object 0 static 0128aa04 EVENT_MAXIMIZEDBOUNDSCHANGED
00000000 4000004 13c 0 instance 00000000 components
7b21a924 4000005 140 …dows.Forms.Button 0 instance 012a8f14 btnLeak
79332f40 4000006 144 …ections.Hashtable 0 instance 0128a638 hshDataHolder

12. Studying the above output, we ascertain the name of the hashtable as hshDataHolder.

This definitely makes it easier to find the related code! Next, it is up to the programmer to refine and fine-tune the lines of code in order to get rid of the memory leak.

h1

Writing Fast Code the easy way Part IV

October 21, 2008

Using Variables

Initializing

We all have a habit of initializing variables with their default values as soon as we declare them. We also have a tendency to assign the object variables to null when we think we don’t need the variable any more.

Well I thought the above procedure to be correct and a standard way, and I myself had advocated the way for quite some time in past until I found the following.

Once space for the object is allocated, it remains to initialize it (construct it). The CLR guarantees that all object references are pre-initialized to null, and all primitive scalar types are initialized to 0, 0.0, false, etc. (Therefore it is unnecessary to redundantly do so in your user-defined constructors. Feel free, of course. But be aware that the JIT compiler currently does not necessarily optimize away your redundant stores.)

What will we benefit if we don’t redundantly initialize the variable? Well it will be the CPU cycles that go into loading the variable in memory and assigning a value. Redundant Initializing is nothing but variable assignment! Same is true when we assign null to object references when they are supposed to go out of scope. GC will automatically collect those variables that have gone out of scope. If the object reference implements IDisposable, call the dispose() for sure, if its has close(), clear(), Stop(), and you don’t need the object reference, make sure to call that method which is supposed to release the resources. Not doing above and setting it to null is leaking memory and in addition wasting CPU cycles.

Scope

When we write a function block we normally declare variables on top of the function, some people nicely declaring the primitive types in a section and object variable in a separate, which make the code look beautiful (I agree on that)

However when we want the GC to work efficiently for us and memory usage/leak is a concern, it’s always better to declare variables very near to the scope the variable will have.

Benefit,

  1. The memory allocation will take place only when required.
  2. The memory de-allocation will take sooner as GC will known that variable is no longer required as soon the variable will exit his scope.

Caching

Caching values is one of the best optimization techniques that I have found so far (of course writing efficient code has no match and I believe caching is part of that).

Always cache a property value if it’s used more more than once, and make a habit to cache a value returned from a function even more as generally the function has to do some data churning, may be DB access to get you the desired output. You can save those CPU cycles and memory allocations/de-allocations if you cache those values.

LOH (Large Object Heap)

I read on MSDN that LOH is not compacted like other generations. On another site I read that heavy allocation and de-allocation of large objects (size more than ~85K) will fragment LOH heavily and sooner or later a time will come that even though we will have sufficient free memory available, but all fragmented with no contiguous block. Requesting large object memory from GC at this juncture will result in OutOfMemory exception.

To address this problem currently I can think of the following:

  1. Don’t create objects which can possibly land up in LOH. Break them into smaller units.
  2. Don’t allocate and de-allocate large objects frequently, especially for server end applications as they are supposed to run 24×7 and they are sure candidate to fall in this trap.
  3. I don’t know for sure if reusing large object variable variables (without de-allocating and re-allocating) will help in this aspect, I will have to test this to be sure. You are welcome to post your comments if you have tried this before.
h1

Writing Fast Code the easy way Part III

October 21, 2008

Switch Vs If

Use of Switch instead of If whenever possible can be quite helpful, especially if its inside a loop or any code block which is executed frequently. It can give a performance boost up to 4 times !

Check out the sample skeleton code below and the performance readings for the same.

public void SwitchTest()

{

int i = 0;

while (LOOPCOUNT >= i++)

{

switch (i)

{

case 10:

break;

case 20:

break;

case 30:

break;

case 40:

break;

case 50:

break;

case 60:

break;

case 70:

break;

}

}

}

public void IfTest()

{

int i = 0;

while (LOOPCOUNT >= i++)

{

if (i == 10)

{

}

else if (i == 20)

{

}

else if (i == 30)

{

}

else if (i == 40)

{

}

else if (i == 50)

{

}

else if (i == 60)

{

}

else if (i == 70)

{

}

}

}



The above readings were generated with exact same code listed above and measured using DevPartner Performance expert. Its highly recommended that before applying any performance tips in your project, make sure to measure the performance yourself.

h1

Writing Fast Code the easy way Part II

October 21, 2008

Writing If conditions

The way we write If conditions can dramatically affect the performance, especially if its in a loop churning out lot of data.

Check out following two sample code (the If conditions are meaningless and are just put to simulate some conditional processing)

public void IfNested()

{

int i = 0;

while (LOOPCOUNT >= i++)

{

if (i > 20)

{

if (i > 40)

{

if (i > 60)

{

}

}

}

}

}

public void IfCombined()

{

int i = 0;

while (LOOPCOUNT >= i++)

{

if (i > 20 && i > 40 && i > 60)

{

}

}

}

Out of the above two, combined condition is a winner as it benefits from conditional short circuiting optimization in .net. It’s also always a good move to put such conditions first in order which can help the runtime to skip executing unnecessary code blocks in advance.

If( CondA && CondB && CondC) is a good one if we know that CondA can be false most of the times. If CondC is the one which can be false most of the times, the if block should be written as If( CondC && CondB && CondA).


The above readings were generated with exact same code listed above and measured using DevPartner Performance expert. Its highly recommended that before applying any performance tips in your project, make sure to measure the performance yourself.

h1

Writing Fast Code the easy way Part I

October 21, 2008

String or StringBuilder?

Which is better string or StringBuilder? We all know that StringBuilder is the better choice. Really? If yes, then by what ratio.

Let’s check it out.

We have following code blocks which perform exactly same, but are written in different ways

private const int LOOPCOUNT = 1000;

private void StringBuilderAppend()

{

StringBuilder _subElementValue = new StringBuilder();

int i = 0;

while (i++ <= LOOPCOUNT)

{

_subElementValue.Append(“_xmlStart”);

_subElementValue.Append(“nav3Name”);

_subElementValue.Append(“_xmlEnd”);

_subElementValue.Append(“nav3.Value”);

_subElementValue.Append(“_xmlEndStart”);

_subElementValue.Append(“nav3Name”);

_subElementValue.Append(“_xmlEnd”);

}

}

private void StringConcatenate()

{

int i = 0;

string sTemp = “”;

while (i++ <= LOOPCOUNT)

{

sTemp += “_xmlStart”;

sTemp += “nav3Name”;

sTemp += “_xmlEnd”;

sTemp += “nav3.Value”;

sTemp += “_xmlEndStart”;

sTemp += “nav3Name”;

sTemp += “_xmlEnd”;

}

}

As expected code block using StringBuilder.Append is quite fast compared to the string += operations. It’s 347 times faster than the string += operations! It’s a clear choice when concatenating multiple strings. What if we need the fastest code to be a little faster without killing the code readability and jumping into unsafe codes?

The following code is much faster than the StringBuilderAppend method. It’s faster by a factor of 2.4 times. What we simply did was used multiple string + operations (note there is no +=) and appended it in a StringBuilder.

private void StringBuilderAndStringConcatenate()

{

StringBuilder _subElementValue = new StringBuilder();

int i = 0;

while (i++ <= LOOPCOUNT)

{

_subElementValue.Append(“_xmlStart” + “nav3Name” + “_xmlEnd” + “nav3.Value” + “_xmlEndStart” +

“nav3Name” + “_xmlEnd”);

}

}


The above readings were generated with exact same code listed above and measured using DevPartner Performance expert. Its highly recommended that before applying any performance tips in your project, make sure to measure the performance yourself.

h1

How to use DevPartner for Memory profiling of .net Windows Services

April 15, 2008

I have using the Compuware Devpartner studio for quite some time for profiling our application for memory and performance, and I found it quite useful. Recently we had a memory leak issue in one of our windows service. I tried using the DevPartner profiling for memory on it, but alas I could not profile a service!!

This was a setback and now I was in a fix. Either I have to explore new profiling tools to get my job done or dive into the vast code and find the issue. I decided to spend some time on finding if there is some other way to profile the windows services using DevPartner. I Googled a lot and could not find anything :(

Later out of frustration I looked into the installation directory, and found lots of exe’s and out of curiosity I opened them one by one. To my pleasant surprise I found one exe named DPAnalysis.exe which was what I wanted. It was a command line tool for doing all sorts of Analysis

  • Performance
  • Coverage
  • Memory
  • Performance expert

Following is the help output from that exe, which explains all the different command line arguments.

Usage:

1) DPAnalysis [a] [b] [c] [d] {e} target [target args]

2) DPAnalysis /config config.xml

a) AnalysisType: Set the run-time analysis type. Performance is default.

/PERF[ORMANCE] Set analysis type to DevPartner Performance Analysis

/COV[ERAGE] Set analysis type to DevPartner Coverage Analysis

/MEM[ORY] Set analysis type to DevPartner Memory Analysis

/EXP[ERT] Set analysis type to DevPartner Performance Expert

b) DataCollection: Enable/Disable data collection for a given target. DOES NOT LAUNCH the target.

/E[NABLE] Enable data collection for the specified process or service

/D[ISABLE] Disable data collection for the specified process or service

c) OtherOptions:

/O[UTPUT] Specify the session file output directory and/or name with optional extension (.dpprf, .dpcov, .dpmem, or .dppxp)

/W[ORKINGDIR] Specify the process’ working directory

/H[OST] Specify target’s host machine

/NOWAIT Don’t wait for process to exit, just wait for it to start

/N[EWCONSOLE] Run the process in its own command window

d) AnalysisOptions:

/NO_MACH5 Disables excluding time spent on other threads

/NM_METHOD_GRANULARITY Set data collection granularity to method-level (line-level is default)

/EXCLUDE_SYSTEM_DLLS Exclude data collection for system dlls (Perf only)

/NM_ALLOW_INLINING Enable run-time instrumentation of inline methods

/NO_OLEHOOKS Disable collection of COM

/NM_TRACK_SYSTEM_OBJECTS Track system object allocation (Memory only)

e) TargetType: Identify target process or service. MUST BE LAST OPTION.

All arguments after the target name/path are passed directly to the target.

/P[ROCESS] Target is an exe filename (followed by arguments to the process)

/S[ERVICE] Target is a service name (followed by arguments to the service)

2)/C[ONFIG] Path to configuration file that includes all startup information Note: no other options can be used with /Config

For profiling the windows service I used the following command”

DPAnalysis /MEM /E /S “my service name”

After running the above using a command prompt, I started the service from service controller, and let the service do its job. I monitored the memory usage and VM Size of service using task manager, and when I was convinced that the memory leak condition is reached, I stopped the service. I was prompted for saving the memory analysis file J, which could be easily opened up in VS2005 (of course because DevPartner was installed)

h1

Use of EventWaitHandle

March 1, 2008

Ever faced a problem where the application does not close even after the main form is closed? If yes, one reason could be the threads you are using. If you are using some threads that run a infinite loop which perform some operation at some intervals with a interval of some minutes or more, and you are using Thread.Sleep(), read further.

Consider the following class code snippet.

public class ThreadTest

{

Thread thSettingsMonitor = null;

bool _stopTimeOutProcessing = false;

public override string ToString()

{

return “Thread running state is : “ + this._stopTimeOutProcessing.ToString();

}

public void Start()

{

thSettingsMonitor = new Thread(new ThreadStart(ReloadSettings));

thSettingsMonitor.Start();

Debug.WriteLine(“~~~ Starting the thread. Hash code: “ + this.GetHashCode().ToString());

}

public void Stop()

{

try

{

//set the variable so that the loop breaks and thread terminates

_stopTimeOutProcessing = true;

}

catch { }

}

private void ReloadSettings()

{

while (!_stopTimeOutProcessing)

{

try

{

//do some operation in the thread

//Mimic by a sleep of 2 seconds

Thread.Sleep(2000);

//wait for 5 minutes

Debug.WriteLine(“~~~ reload thread waiting on sleep for 5 min. Hash code: “ + GetHashCode().ToString());

Thread.Sleep(30000);

Debug.WriteLine(“~~~ Sleep period completed”);

}

catch (Exception ex)

{

Debug.WriteLine(ex);

}

}

}

}

Now in a windows application form write the following code

private ThreadTest othTest = new ThreadTest();

private void button1_Click(object sender, EventArgs e)

{

//Start the reloadsettings thread

othTest.Start();

}

private void FrmTest_FormClosing(object sender, FormClosingEventArgs e)

{

othTest.Stop();

}

Run the code, click on the button so that the thread starts running. Try closing the form. Note that the form closes but the .net IDE does not breaks until some time. This means that some code is still executing. Click on the pause button of .net IDE to see which code is running. You will notice that the thread code Thread.Sleep(30000); is still executing.

Now try the updated code.

public class ThreadTest

{

Thread thSettingsMonitor = null;

bool _stopTimeOutProcessing = false;

private EventWaitHandle _exitEvent = new EventWaitHandle(false, EventResetMode.ManualReset);

public override string ToString()

{

return “Thread running state is : “ + this._stopTimeOutProcessing.ToString();

}

public void Start()

{

thSettingsMonitor = new Thread(new ThreadStart(ReloadSettings));

thSettingsMonitor.Start();

Debug.WriteLine(“~~~ Starting the thread. Hash code: “ + this.GetHashCode().ToString());

}

public void Stop()

{

try

{

//set the variable so that the loop breaks and thread terminates

_stopTimeOutProcessing = true;

if (_exitEvent != null)

{

Debug.WriteLine(“~~~ Signalling the timeout thread. Hash code: “ + this.GetHashCode().ToString());

_exitEvent.Set();

Debug.WriteLine(“~~~ Signalling the timeout thread done. Hash code: “ + this.GetHashCode().ToString());

}

}

catch { }

}

private void ReloadSettings()

{

while (!_stopTimeOutProcessing)

{

try

{

//do some operation in the thread

//Mimic by a sleep of 2 seconds

Thread.Sleep(2000);

//wait for 5 minutes

Debug.WriteLine(“~~~ reload thread waiting on eventwait object for 5 min. Hash code: “ + GetHashCode().ToString());

_exitEvent.WaitOne(300000, true);

Debug.WriteLine(“~~~ done waiting or the eventwait object was signaled”);

}

catch (Exception ex)

{

Debug.WriteLine(ex);

}

}

}

}

Run the code and note the difference. This time the application closes immediately.

Let’s understand what was different.

private EventWaitHandle _exitEvent = new EventWaitHandle(false, EventResetMode.ManualReset);

Here we created a thread synchronization object with initial state as non signaled and mode as manual.

Instead of Thread.Sleep(2000); we have now used _exitEvent.WaitOne(300000, true); this instructs the thread to wait for 5 minutes (30000 msec) and exit the wait before the time is elapsed if the event object is signaled.

During form close we have used _exitEvent.Set();. This code signals the event object. When the event object is signaled the thread comes out of wait immediately even before the wait time is completed. The EventWaitHandle is a versatile object when it comes to thread synchronization. Explore it if you are writing multi threaded applications.

PS: The above problem can be easily worked out in normal scenario by setting the thread as a background thread. The only problem it will have is it will be aborted immediately during application close.


Happy coding!

h1

Override ToString()

March 1, 2008

.Net IDE provides excellent support for debugging. One of the supporting tools is the Watch window. It does a very good job when it comes to displaying of data, but it doesn’t always display the data in the best form, especially for the classes that we write. But that is not the fault of the Watch window, its we the developers who are not providing correct implementation of ToString() to our classes.

The .net debugger calls the ToString() automatically on the instance which is to be monitored in the watch window. If we override the ToString() implementation in our class and provide the key fields as part of implementation, that will make our job a lot easier when it comes to debugging.

See the following code listing. Run it with the ToString() method commented and add a instance of the clsExample class nin watch window and notice the data. Compare it with the original verison.


using System.Diagnostics;

namespace DebuggererAttribute

{

public class clsExample

{

private int _FieldOne;

private double _FieldTwo;

private string _FieldThree;

private bool _FieldFour;

public int FieldOne

{

get

{

return _FieldOne;

}

set

{

_FieldOne = value;

}

}

public double FieldTwo

{

get

{

return _FieldTwo;

}

set

{

_FieldTwo = value;

}

}

public string FieldThree

{

get

{

return _FieldThree;

}

set

{

_FieldThree = value;

}

}

public bool FieldFour

{

get

{

return _FieldFour;

}

set

{

_FieldFour = value;

}

}

public override string ToString()

{

return string.Format(“FieldOne = {0}, FieldTwo = {1}, FieldThree = {2}, FieldFour = {3}”, _FieldOne, _FieldTwo, _FieldThree, _FieldFour);

}

public clsExample()

{

_FieldOne = 2;

_FieldTwo = 3.66;

_FieldThree = “This is some text”;

_FieldFour = true;

}

}

}

h1

AutoResetEvent Object and Hanged Application

March 1, 2008

This is something related to a bug which was not so reproduce able , but was seen once a week at least on one of the many test machines. We used to get a “Server busy” dialog box and Switching or Retrying did not do anything. We have to kill the application.

I was accustom to see this dialog when a COM server is busy or is waiting for some resource and usually after some time, Switching or Retrying worked.

Fortunately i got this error on my development machine once and more fortunately i was running a debug build. I paused the program and watched the call stack. The program was waiting on a thread which was in a “Wait or Sleep/Join state”. Drilling down more on the thread, it was found that it was one of the our own application thread.

This information gave me a good deal of information on where to look for. I checked the module which created the thread and found that it was created in a user control and the thread was using a AutoResetEvent object. The wait state of the thread as i discovered earlier from call stack was from the Dispose call of the user control. I checked the code to see the places where the AutoResetEvent was closed or signaled. Well the object was not cleaned up/closed in the Dispose call “which was the problem”.

The thread was waiting on the AutoResetEvent object and was not allowing the user control to Dispose and hence the application to close.

Learning:
1. Clean up all variables manually, don’t rely on GC. GC does a good job, but we screw up our selves thinking GC will take care.
2. Use of thread and windows kernel objects like Mutex, Semaphores etc (thread synch objects) are a double edged sword. Always use them with caution.

h1

How much a Thread cost you?

January 8, 2008

I was recently wondering if creating n number of threads will affect the memory usage of an application. I created a sample application which created a thread on a button click to observe the memory usage on each thread creation. The thread proc as such did nothing; I had put a sleep statement in it so that the thread is alive and is using the memory for some time while I am creating some more threads.

I was relaxed to see that the memory usage in task manager rose by just 20K -30 K on each thread creation with an exception that the first thread creation increased the memory by around 1 MB.


Later I started checking Virtual memory usage as well and was shocked to see 1 MB of memory usage on each thread creation! A little goggling revealed that by default each thread is assigned a 1 MB stack! Check out the sample code which prints out the Virtual Memory usage of the test application in debug window on each thread creation.

//code
private static void Somethread()
{
Debug.WriteLine(string.Format(“Started thread with thread id {0}”, Thread.CurrentThread.ManagedThreadId));
//Print task managers VM Size column
Debug.WriteLine(string.Format(“Current VM usage is {0} MB”, (Process.GetCurrentProcess().PagedMemorySize64 / (1024 * 1024))));

Thread.Sleep(1000 * 60 * 1); // let the thread live for some time while we create some more threads
Debug.WriteLine(string.Format(“—-Ended thread with thread id {0}”, Thread.CurrentThread.ManagedThreadId));

//Print task managers VM Size column
Debug.WriteLine(string.Format(“Current VM usage is {0} MB”, (Process.GetCurrentProcess().PagedMemorySize64 / (1024 * 1024))));

}

private void btnCreateThread_Click(object sender, EventArgs e)
{
//Each created thread will be allocated 1 MB of Virtual memory
Thread samplethread = new Thread(Somethread);
samplethread.IsBackground = true;
samplethread.Start();
}

Conclusion: Want to keep a small footprint of the application, think twice before creating a new thread.

Follow

Get every new post delivered to your Inbox.