Click here to Skip to main content
15,885,546 members
Articles / Programming Languages / C#

Common C#.NET Performance Guidelines

Rate me:
Please Sign up or sign in to vote.
3.00/5 (10 votes)
8 Sep 2011CPOL8 min read 34.8K   16   8
Common C#.NET Performance Guidelines

String Operations

Review your code which performs intensive string manipulations.

‘+’ Operator

When number of operands is known, perform string concatenate using + operator.

e.g.

String str = "abc" + "xyz" + "pqr" 

Why -

StringBuilder Class

When number of operand is unknown, perform string concatenate using StringBuilder.Append(). Such as string concatenation in loop , you should use StringBuilder

e.g.

C#
for(int i=0; i<Results.count; i++)
{
	StringBuilder.Append(Results[i])
}

Some example to demonstrate StringBuilder:

C#
//prefer this
StringBuilder sb;
Sb.Append(str1);
Sb.Append(str2);

//Over this
Sb.Append(str1+str2);

//prefer this for concatenate string for various functions
Void F1(sb, …);
Void F2(sb, …);

//Over this
StringBuilder sb;
Sb.Append(F1(…));
Sb.Append(F2(…));

String.Compare()

When performing case-insensitive string comparisons, Check for the lines which calls ToLower() as these are not required for performing case-insensitive comparisons

C#
String.Compare(String str1, String str2, bool ignoreCase);

Calling ToLower() method & then comparing will require temporary string allocation which can be expensive when called in Loop

Looping & Recursions

Slight inefficiency in looping & recursions is magnified due to it being repeatedly called.

Recursions

  • In some scenarios looping is preferable than using recursion because recursive call builds new stack frame for calls which results in memory consumption
  • Looping does not require building stack frame unless a method is called inside a loop
  • Make sure the recursive call have a way out & there is no danger of running out of stack space

Foreach(C#)

Foreach provides an enumerator by overrriding IEnumerable.GetEnumerator. This causes to add both managed heap & virtual function overhead on simple types

Use for loop instead of foreach to iterate through simple type array OR collection (built in value types such as int, char etc..) in performance critical code.

Properties

Though properties looks like a field it has hidden costs. You should design the classes accordingly to define properties & fields as required

You need to be-aware that when the property is accessed, additional code such as validation check can be executed. Hence this might be slower than accessing a field. Simple properties like setting or getting the private member variable there is no performance difference as compared to accessing a field. However if you use virtual properties it become easily complicated as virtual properties cannot be in-lined

Repeatedly accessing a property in for loop becomes more expensive. So if possibly it should be read outside the for loop

Optimize Loops

Examine the code in loop to find the opportunities to optimize it. Some of them are as mentioned below

  • Move out any code that does not change inside the loop
  • Use StringBuilder for concatenating the strings inside the loop
  • Considering inlining the code instead of calling the function which contain small amount of code
  • If you test multiple condition inside the loop, begin the expression which will most likely allow you to exit
  • Avoid calling properties inside a loop.

Exception Handling

Exception handling by try/catch block is recommended way to handle exceptional error condition in managed code. Improper managed exceptions can significantly affect performance

Finally block

Make sure you use finally block to free up your resources. Finally block is always executed, even if an exception occurs

e.g.:

C#
try
{
  conn.Open(); // assume some connection object, which implements IDisposable
}
finally
{
 if(null!=conn)
   conn.Close();  // Always executed even if an exception occurs
}

Else you can use using construct in C#, which call the dispose at end of the construct, assuming the required resource implements the Idisposable

C#
Using(conn)
{
 conn.open();
}

Rethrow exception

The cost of using throw to rethrow a existing exception is same a throwing the new exception.

C#
Try{
//do something which can throw an exception
}
Catch(Exception e){
Throw;
}

In above e.g. there is no saving from re-throwing the existing exception. You should consider wrapping an re-throwing the exception on when it provides additional diagnostic information.

Loops

Check if your code throws an exception inside for loop. This should be avoided, place your try/catch block outside the for loop.

Reduce unnecessary exception

Do not catch exception which you cannot handle. You should catch exception to provide some debugging information (exception details) or retry any failed information.

Avoid catching generic exception, this leads to catching all exception & most of these exceptions are re-thrown eventually.

C#
Catch(Exception e)
{
}

Explicitly name the exception to avoid catching & re-throwing. Below code catches all System.IO exceptions:

C#
Catch(System.IO)
{
}

Preserve as much as diagnostic information as possible in your exception handlers

Avoid exception to control application flow

Do not use exception to control your application flow. If you except events in normal course of code execution, you should not throw an exception. In below e.g. exception is thrown inappropriately when customer name is not found

C#
Static void nameExists(String name)
{
If(cr.read(name) == 0)
{
Throw(new Exception("name not found"));
}
} 

Name not found is expected condition, so re-factor the code to return value instead of throwing exception

C#
Static bool nameExists(String name)
{
	If(cr.read(name) == 0)
	{
		Return false;
}
}

Locking & Synchronization

Mutex object

Tradeoff for mutex object is when you require interprocess synchronization. Do not use mutex to cross –synchronization of threads within a single process. Mutex object is more expensive as this is kernel object, hence provides cross-process synchronization

Lock”this”

For correctness reasons avoid locking on this object. Instead provide a private object to lock on. Below is example

C#
//prefer this
Class A{
Private Object mylock = new Object();
Lock(myLock)
{  …  }
}

//over this
Class A{
Lock(this)
{    …   }
}

“This” is external visible object & you never know what other code might be acquiring this same lock

If you require a atomic updates to your member variable use System.Threading.Interlocked class

C#
Lock typeof(object)

Avoid locking type of the object, because there might be other threads in same process that lock on same type of object. Hence it might cause your code to hang until the thread releases the lock on same type of object

This also creates a potential to create deadlocks. Prefer using a private static object in your class to provide synchronization

C#
//prefer this
Class A{
Private static Object _lock = new object();
Lock(_lock)
{   ….   }
}

//over this
Lock(typeof(myObject))

ReaderWriterLock

Tradeoff for using ReaderWriterLock is when you require multiple threads to read the resource concurrently & synchronized access to write the resource. Hence during such scenarios ReaderWriter lock should be preferred over Lock & Monitor locking mechanisms

Threading

ThreadStart

Indiscriminately spawning the threads can easily reduce your application performance rather than improving. Frequently creating new threads can lead to extensive context switching, memory allocation & additional cleanup when a thread dies

Recycling threads with thread pools results in superior quality, then spawning new thread for new requests

Below code shows new thread been created & maintained for each page load

C#
Private void page_load(Object o, System.EventArgs e){
If (page.isPastBack)
{
	ThreadStart ts = new ThreadStart(callFunc);
	Thread tr = new Thread(ts);
	Tr.start();
	….
}

ThreadPool

Use CLR thread pool to execute thread based work, to avoid expensive thread creation OR initialization. Below code shows method been executed using a thread from thread pool

C#
WaitCallBack methodTarget = new WaitCallBack(MyClass.updateCache)
ThreadPool.QueueUserWorkItem(methodTarget);

ThreadPool class uses a thread from application pool to execute the method passed in callback as soon a thread is available

System.Threading.Timer

Use Timer class to perform periodic tasks. Timer class provides execution of method by specifying the time interval. Each time the timer elasped a thread from a thread pool is used to execute the method indicated in TimerCallBack.

Below code shows calling the myFunc() method every 30 secs

C#
TimerCallBack myCallBack = new TimerCallBack(myFunc);
Timer tr = new System.Threading.Timer(myCallBack, null, 0, 30000,);
…
Static void myFunc(object state)
{
	…..
}

This results in optimal performance because it avoids thread initialization incurred in spawning new thread

Thread.Abort

Aviod using Thread.Abort for terminating other threads. Abort caused CLR to throw a ThreadAbortException on a thread to be terminated. You can use Thread.Join to wait on the thread to make sure that the thread has terminated

Thread.Resume / Thread.suspend

Never call Thread.Resume OR Thread.suspend to synchronize the activities between the threads. Never call suspend to suspend low priority thread, instead consider setting Thread.Priority property

Calling suspend on one thread from other can cause application deadlock. For example you might suspend a thread holding May resources needed by other threads

If you want to synchronize activities between multiple threads try using synchronization objects like lock, mutex, monitor, Events etc..

Memory Management

Call Dispose OR Close

Your code should call Dispose or Close on all objects which supports this methods. For e.g. all the objects which implements Idisposable

Common disposable objects are as follows:

  • Database – Connection, DataReader & Transaction
  • File – FileStream, BinaryWriter
  • Stream Based – StreamReader/Writer, TextReader/Writer, BinaryReader/Writer

Also, check Finally & Using blocks to ensure resources are released

Complex Object Graphs

Candidates for the complex object graph can be those with many references to other objects. Complex Object graphs can result in additional work for garbage collectors to create & allocate these objects. Identify opportunities to simplify the structures & classes to have good heap locality & easier to maintain

Also, identify the problem where short-lived objects are referenced from long-lived objects. As a result of this short-lived object are been promoted from generation 0 & increases burden on garbage collector

WeakReference Objects

Consider using WeakReference when working with cached data, So that cached objects can be resurrected when needed or released by garbage collector when there is memory pressure. WeakReference is suitable for medium to large sized objects stored in collection

e.g. of WeakReference implementation is as below:

C#
Void someMethod()
{
	ArrayList arList = new ArrayList[5];
	MyObject obj = new MyObject();
	WeakReference wr = new WeakReference(obj);
	arList.add(wr);
	//retrive Weak reference
	WeakReference wr = (WeakReference)arList[0];
	//Create new myObj to be assigned
	MyObject myObj = Null;
	If(Weakreference.IsAlive)
		myObj = (MyObject)wr.Target; 	//if this object is not collected 
						//by garbage collector
	if(MyObj == NULL)
{
 	//As object is collected by GC, surrect again
}
}

GC.Collect

Garbage collector is self tuning, by programmatically forcing a collection you might hinder the performance rather than improve. Your code should not call GC.Collect explicitly

Finalizers

Use finalization for the objects that need to perform cleanup task during collection process & just before the object memory is reclaimed. Finalizers are mainly use to release unmanaged resources (database connection, file handles, COM object reference) maintained by object

Use below consideration for writing Finalizers

  • Implement finalizer only for the object which holds unmanaged code. Unnecessary finalizers adds extra load to finalizer thread as well as garbage collector
  • Class that implements finalizers should also implements Idisposable and the dispose function should use supressFinalization if the cleanup is already been performed in dispose function
  • Dispose methods should call dispose of base class as well as dispose of class members
  • Cleanup code in Finalizers should be thread safe for thread safe types

Boxing & Unboxing

Boxing & unboxing enables value types to be treated as objects (reference). Boxing a value type packages it inside an instance of type Object reference type. This results in storing value type in managed heap. Unboxing is reversed process which extracts value from the object type

Consider below example

C#
Int I = 111;
Object o = (Object) I; 	//boxing – allocated value type on managed heap by 
			//creating new object
O = 222;
I = (int)o; //unboxing

Boxing & unboxing are computationally expensive process i.e. when a value type is boxed entirely new object has to be created & allocated

Some considerations:

  • Avoid passing value types to the method which expect a reference type
  • Pay attention to code in loops where boxing overhead can quickly add up
  • Consider using arrays or collection of custom-type class rather than using collection of System.Objects. For example consider arrays to store integer value type instead of using ArrayList
  • You can measure your assembly for box & unbox instruction by using below command line

Ildasm.exe yourcomponent.dll /text | findstr box (OR unbox)

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior)
India India
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
GeneralMy vote of 5 Pin
Waseem-Malik25-Sep-13 1:21
professionalWaseem-Malik25-Sep-13 1:21 
GeneralMy vote of 1 Pin
Joe Sonderegger9-Sep-11 5:45
Joe Sonderegger9-Sep-11 5:45 
QuestionA good start of an article Pin
Keith Rule8-Sep-11 9:39
professionalKeith Rule8-Sep-11 9:39 
CSS
"Programmers waste enormous amounts of time thinking about, or worrying about, the speed of noncritical parts of their programs, and these attempts at efficiency actually have a strong negative impact when debugging and maintenance are considered. We should forget about small efficiencies, say about 97% of the time: premature optimization is the root of all evil. Yet we should not pass up our opportunities in that critical 3%."  Donald Knuth

This article is clearly targeted at that 3% of the time were we need a little help with efficiency. I understand the criticism that others have made. But I believe they are being a bit harsh. That 3% is worth an article.

I think this is a very good topic that is rarely discussed. You have an interesting start to your article. I’d encourage you to show running code examples with timing. And probably some il generated code to show the effect of alternative C# coding options.

Thanks for the effort!<pre>
<div class="signature">Keith Rule</div>

AnswerRe: A good start of an article Pin
Oshtri Deka8-Sep-11 23:52
professionalOshtri Deka8-Sep-11 23:52 
GeneralMy vote of 1 Pin
canozurdo8-Sep-11 9:03
canozurdo8-Sep-11 9:03 
GeneralMy vote of 1 Pin
zecanard8-Sep-11 7:28
zecanard8-Sep-11 7:28 
GeneralRe: My vote of 1 Pin
Oshtri Deka8-Sep-11 23:54
professionalOshtri Deka8-Sep-11 23:54 
SuggestionWrong WeakReference usage... Pin
Xetrill8-Sep-11 5:11
Xetrill8-Sep-11 5:11 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.