C# Code Optimization – Methods and Techniques


These are my notes based on a online course I took on C# optimization. They might be usefull to someone elese also:

They are based on this Udemy course: https://www.udemy.com/csharp-performance-tricks-how-to-radically-optimize-your-code

C# Code optimization


Optimization strategies. 2

Warning. 2

.NET Fundamentals. 3

Stack. 3

Heap. 4

Value types. 5

Value types options. 6

Reference type. 6

Boxing and unboxing. 6

Immutable String. 6

Common Intermediate Language. 7

Instructions. 8

Key points. 9

Basic optimizations. 9

Avoid boxing and unboxing. 9

Slow Collections and other data holders. 9

Fast Collections. 9

Summary on boxing and unboxing. 10

String concatenation. 11

Appending to a string. 11

Appending to a StringBuilder. 11

Summary. 11

Collections. 12

Collections in .NET. 12

Summary. 12

Arrays. 12

Exceptions. 12

For vs Foreach. 13

Pros and cons. 13

Summary. 13

How to use for and foreach. 13

Intermediate optimization. 13

Fast garbage collection. 13

GC Problems. 13

Implicit assumptions about GC. 14

Gen:0 optimizations. 14

Lifetime optimizations. 14

Size optimizations. 15

Summary. 16

Fast delegates. 17

Sample code – Using delegates. 17

Fast class factories. 18

Sample solution. 18

Advanced optimization. 19

Arrays on the stack. 19

Sample code. 19

Pointers. 19

Pointers in C#. 19

Summary. 20

Optimization strategies

· Rewrite algorithms to achieve same results with less code

· Avoid unnecessary instructions

· Use libraries specifically designed for the task at hand

· Reduce memory consumption as much as possible

· Avoid scenarios where the code is blocking and waiting for a slow resource


· Do not try to optimize in advance since you can not know how your application will evolve and behave.

o The exception: Perhaps when you know what you are doing and even then at certain specific things not the entire or most of the application. Like when you clearly know certain elements and things which might become problematic because of X things like huge data processing in a certain situation which is known to be present in the application.

o The aim is to write simple, clear and modular code. Leave optimization to the very end, when you reliably know where the bottlenecks are.

.NET Fundamentals


· Information is stack upon one another as stack frame containing method parameters, local variables, return address etc.

· Tracks methods calls

· Contains frames which hold parameters, return address and local variables for each method call

· A stack frame is removed when returning from a method. All local variables go out of scope at this point

· If you call too many methods, the stack will fill up completely and .NET throws a StackOverflow exception


· Code that uses integers on the stack is slightly faster than code that uses objects on the heap.

· The new keyword creates objects on the heap

· Code that uses objects on the heap is slightly slower than code using integers on the stack.

· When variables on the stack go out of scope, their corresponding objects on the heap are dereferenced, not destroyed. The .NET Framework will always postpone cleaning up dereferenced objects.

· The .NET Framework eventually starts a process called Garbage Collection and deallocates all dereferenced objects on the heap.

Value types

· Value types store their value directly

o the type and value is store at the same location in the stack

· Value types can exist on the stack and the heap

o A value type created with an object

· Value types are assigned by value

o Data/value is copied from one value to another

· Value types are compared by value

Value types options

· sbyte

· byte

· char

· short

· ushort

· int

· uint

· long

· long

· float

· double

· decimal

· bool

· enum

· struct

Reference type

· Reference types can be set to null

· Reference type store a reference to a value on the heap

· Reference types can exist on the stack and the heap

o In the heap within an object

· Reference types are assigned by reference

o You can have multiple references to an instance of the object

· Reference types are compared by reference

o Meaning they compare by address

Boxing and unboxing

· Boxing takes a value type on the stack and stores it as an object on the heap

· Boxing happens when you assign a value type to a variable, parameter, field or property of type object

· Unboxing unpacks a boxed object on the heap, and copies the value type inside back to the stack

· Unboxing happens when you cast an object value to a value type

· Boxing and unboxing negatively affect performance

Immutable String

· Strings are reference types, and immutable

o Instead of modifying the original data, a new data is created and that is passed back. The original string is left untouched.

· Strings behave as if they are value types. They are assigned and compared by value

· Strings are thread-safe

· Strings are fast

o Fast assignment: Copy a string by copying the reference

o Fast comparison: Compare two interned strings by comparing the reference

· Strings conserve memory

o Memory savings: Identical strings can be merged

Common Intermediate Language

Why two compilations with .NET?

· JIT compiler can optimise for specific CPU

· Creates portable code that runs on any platform

· Code can be verified before running

· Code can be annotated with attributes

· Disadvantages: Slightly slower than direct compilation


· ldc.i4.1

o Load the 4-byte signed integer constant 1 on the evaluation stack.

· ldloc.0

o Load the variable in location 0 on the evaluation stack. The other value on the stack is pushed down.

· add

o Add the top two numbers on the evaluation stack together, and replace them with the result

· stloc.0

o Remove the value at the top of the evaluation stack, and store it in location 0

· box: box the top value on the stack

· bne: branch if top 2 values on stack are not equal

· call: call a static member of a class

· callvirt: call an instance member of a class

· ldelem/stelem: load and store array elements

· newarr: create a new 1-dimensional array

· newobj: create a new object on the heap

· ret: return from method

· throw: throw an exception

· unbox: unbox the top reference on the stack

Key points

· JIT compilation optimizes for local hardware

· IL code is portable, can be verified and annotated

· IL uses local variable locations and an evaluation stack

· Built-in support for objects

· Built-in support for 1-dimensional arrays

Basic optimizations

· Easy to implement, may require only one line of code to change

Avoid boxing and unboxing

· Boxing and unboxing causes an overhead when active

· The difference can be multiple times slower performance when unboxing and boxing

o Part of the reason for the slowness is because moving data from the stack and head

Slow Collections and other data holders

· These are classes which use object arrays internally for storing data


· ArrayList

· CollectionBase

· DictionaryBase

· HashTable

· Queue

· SortedList

· Stack

· HybridDictionary

· ListDictionary

· NameObjectCollectionBase

· OrderedDictionary


· DataSet

· DataTable

· DataRow

Fast Collections


· Generic collections use typed arrays for storing data

· Dictionary

· HashSet

· LinkedList

· List

· Queue

· SortedDictionary

· SortedList

· SortedSet

· Stack


· DataReader reads data directly without casting to object

· DataReader


· Consider using one dimensional arrays when you know the array size

Summary on boxing and unboxing

· Casting object variables to value types introduces an UNBOX instruction in intermediate code

· Storing value types in object variables introduces a BOX instruction in intermediate code

· Avoid casting to and from object in mission critical code

· Avoid using non-generic collections in mission critical code

· Avoid using DataTables in mission critical code, but only if you perform many operations on the same data table object

String concatenation

Appending to a string

Each append to the string creates a new copy and leaves the dereferenced original on the heap.

Appending to a StringBuilder

Each append to the StringBuilder writes into available buffer space.


· if you’re adding strings together in your code, always use the StringBuilder class

· Try to avoid adding strings using the + operator

· If you add 4 strings or less, use regular string variables and the + operator for the best performance

· If you add 4 strings or more, or you don’t know the number of additions in advance, use a StringBuilder

· For ten thousand additions, the StringBuilder is more than 240 times faster than regular string addition.


Collections in .NET

· Collection classes in System.Collections

· Generic collections in System.Collections.Generic

· Typed arrays


· Always use the generic collection classes from the System.Collections.Generic namespace in mission critical code

o Example: List<int> => Uses internally a native integer array instead of object array. This way unnecessary boxing is avoided.

· Avoid the classes in the System.Collection namespace in mission critical code.

· If you have mission critical code, and the number of elements is known in advance, use an array.

o compilers have a native support for arrays

o Also the arrays are pre-initialized. Notice that you can pre-initialize to a certain size in collections also.

· If you have mission critical code, and the number of elements is not known in advance, use a generic list

· Avoid the classes in System.Collections


· If you only have 1 dimension of data, use 1-dimensional arrays for the best performance.

· If you have 2 dimensions of data, flatten the array.

o Access the data with the following formula:

§ int index = 3 * row + col;

· If this is not possible, consider using a jagged array.

· If there is no other option, use a 2-dimensional array


· Never use exceptions in mission-critical code

o This means throwing exception from your mission critical code

· Use exceptions for fatal conditions that require an abort.

o Mostly situations on resources which you have no power over such as external physical devices, memory errors etc

· Don’t put try-catch blocks in deeply nested code

o Such as low level API calls

· Never use catch(Exception) to catch all exceptions

· Do not use exceptions for non-critical conditions

o Example – Parsing: Instead of catching exceptions with parsing use the TryParse function instead of Parse which has extra exception handling

§ In other words check if something is the way you want it instead of relying on exceptions to check if something is valid

· Never use exceptions to control the flow of your program

For vs Foreach

Pros and cons


· Pro: fastest but requires an indexer

· Con: indexer needs all values loaded in memory


· Pro: works on any collection

· Pro: loads values on demand

· Con: slower because it requires an enumerator


How to use for and foreach

· Array: do not refactor code, not worth it.

· List<>: refactor foreach to for to get 1.6x improvement

· ArrayList: refactor foreach to for to get 2.8x improvement, but consider using List<> instead

Non-generic enumerators return the current value as an object. Do not use them for value types to avoid boxing and unboxing. Always use generic enumerators if possible.

For value type collections, enumerate over IEnumerable, not IEnumerable, to avoid boxing and unboxing

Intermediate optimization

Fast garbage collection

GC Problems

First problem

· A mark and sweep Garbage Collector has to mark all live objects on the heap during each collection cycle.

· This can lead to long program freezes while the collection is running

· Also: repeatedly marking the same objects over and over in each cycle is very inefficient

Solution: Generational Garbage Collector

Second problem

· During 2 successive garbage collection cycles a long-living object will be compacted twice and moved twice: 4 memcopy operations for a single object

· For long-lived very large objects these 4 memory copy operations impact performance

Solution: Small Object and Large Object heaps

Implicit assumptions about GC

· Objects are either short-lived or long-lived

· Short-lived objects will be allocated and discarded within a single collection cycle

· Objects that survive two collection cycles are long-lived

· 90% of all small objects are short-lived

· All large objects (85K+) are long-lived

· Do not go against these assumptions

Gen:0 optimizations

· The more objects in generation 0, the more work the Garbage Collector has to do. So:

· Limit the number of objects you create

· Allocate, use, and discard objects as quickly as possible

Sample code


StringBuilder s = new StringBuilder(); for (int i=0; i < 10000; i++) { s.Append(i.ToString() + “KB”); }:

ArrayList list = new ArrayList(); for (int i=0; i < 10000; i++) { list.Add(i); }

public static MyObject obj = new MyObject(); … // lots of other code … UseTheObject(obj);


StringBuilder s = new StringBuilder(); for (int i=0; i < 10000; i++) { s.Append(i); s.Append(“KB”); }

List<int> list = new List<int>(); for (int i=0; i < 10000; i++) { list.Add(i); }

MyObject obj = new MyObject(); UseTheObject(obj); obj = null;

Lifetime optimizations

The Garbage Collector assumes 90% of all small objects are short-lived, and all large objects are long-lived. So:

· Avoid large short-lived objects

· Avoid small long-lived objects

Kuva 1: The areas where you want your objects to be

You want your object to be:

· Shor-lived + Small objects

· Long-lived + Large Object

Reuse objects (Object pooling)


ArrayList list = new ArrayList(85190); UseTheList(list); … list = new ArrayList(85190); UseTheList(list);


ArrayList list = new ArrayList(85190); UseTheList(list); … list.Clear(); UseTheList(list);

Reallocate after use (refactor code)


ArrayList list = new ArrayList(85190); for (int i=0; i < 10000; i++) { list.Add(new Pair(i, i+1)); }


int[] list_1 = new int[85190]; int[] list_2 = new int[85190]; for (int i=0; i < 10000; i++) { list_1[i] = i; list_2[i] = i + 1; }

Size optimizations

The Garbage Collector assumes 90% of all small objects are short-lived, and all large objects are long-lived. So:

· Avoid large short-lived objects

· Avoid small long-lived objects

Split objects(reduce footprint)


int[] buffer = new int[32768]; for (int i=0; i < buffer.Length; i++) { buffer[i] = GetByte(i); }


byte[] buffer = new byte[32768]; for (int i=0; i < 10000; i++) { buffer[i] = GetByte(i); }

Merge objects(resize lists)


public static ArrayList list = new ArrayList(); … // lots of other code … UseTheList(list);


public static ArrayList list = new ArrayList(85190); … // lots of other code … UseTheList(list);


· The Garbage Collector uses a mark/sweep/compact cycle

· Small objects are allocated on the Small Object Heap (SOH), large objects on the Large Object Heap (LOH)

o A large object is defined by having a size of 85 KB

· The Small Object Heap (SOH) has 3 generations. Objects are allocated in gen 0 and progress towards gen 2.

o Gen 0 is collected frequently, gens 1 and 2 infrequently

· The LOH has only one gen and does not compact

· .NET assumes 90% of all small objects are short-lived

· .NET assumes all large objects are long-lived

· Limit the number of objects you create

· Allocate, use, and discard small objects as fast as possible

· Re-use large object

· Use only small short-lived, and large long-lived objects

· Increase lifetime or decrease size of large short-lived objects

· Decrease lifetime or increase size of small long-lived objects

Fast delegates

· Use delegates in your code where it’s convenient.

· Remove delegates from mission critical code sections for a 9% performance boost.

· Always avoid multicast delegates in mission critical code, they are more than twice as slow as unicast delegates.

Sample code – Using delegates

// declare delegate

public delegate void AddDelegate (int a, int b, out int result);

// set up first addition method

private static void Add1 (int a, int b, out int result)


result = a + b;


// set up second addition method

private static void Add2 (int a, int b, out int result)


result = a + b;


Manually using

Add1 (1234, 2345, out result);

Add2 (1234, 2345, out result);

Unicast using

AddDelegate add1 = Add1;

AddDelegate add2 = Add2;

add1 (1234, 2345, out result);

add2 (1234, 2345, out result);

Multicast using

AddDelegate multiAdd = Add1;

multiAdd += Add2;

multiAdd (1234, 2345, out result);

Tips – Do not do

public static void Main(object[] args) {

myFunc = () => { };

myFunc += GetDelegateOrMaybeNull();



Don’t do this! The code is twice as slow now. Always use unicast delegates and check for null

Fast class factories

A class factory is a special class that constructs other classes on demand, based on external configuration data.

A class factory is a class that constructs class instances on demand using external configuration information.

· The Activator class is 86 times slower than compiled code.

· Dynamic method delegates are 5 times slower than compiled code and 17 times faster than the Activator class.

Replace the Activator class with dynamic method delegates in your class factories to speed up your code by a factor of 17!

Sample solution

· 1. Check dictionary if the delegate has been created already

· 2. If so -> retrieve and call delegate

· 3. If not ->

o 4. create dynamic method and write newObj and ret instructions into it.

o 5. Wrap method in a delegate and store in dictionary

o 6. Call delegate to instantiate class


// a delegate to create the object

private delegate object ClassCreator ();

// dictionary to cache class creators

private static Dictionary<string, ClassCreator> ClassCreators = new Dictionary<string, ClassCreator> ();

private static ClassCreator GetClassCreator (string typeName)


// get delegate from dictionary

if (ClassCreators.ContainsKey (typeName))

return ClassCreators [typeName];

// get the default constructor of the type

Type t = Type.GetType (typeName);

ConstructorInfo ctor = t.GetConstructor (new Type[0]);

// create a new dynamic method that constructs and returns the type

string methodName = t.Name + “Ctor”;

DynamicMethod dm = new DynamicMethod (methodName, t, new Type[0], typeof(object).Module);

ILGenerator lgen = dm.GetILGenerator ();

lgen.Emit (OpCodes.Newobj, ctor);

lgen.Emit (OpCodes.Ret);

// add delegate to dictionary and return

ClassCreator creator = (ClassCreator)dm.CreateDelegate (typeof(ClassCreator));

ClassCreators.Add (typeName, creator);

// return a delegate to the method

return creator;


ClassCreator classCreator = GetClassCreator (typeName);

classCreator ();

Advanced optimization

Arrays on the stack

Don’t use stack-based arrays to improve performance!

· Unreliable benefits, sometimes slower than native arrays

· Limited stack space available for data

· Results in unsafe code

Only use for interfacing with unmanaged code.

Sample code



int* list = stackalloc int[elements];

for (int r = 0; r < repetitions; r++)


for (int i = 0; i < elements; i++)


list [i] = i;





Pointers in C#

· To aid interoperability with unmanaged code

· To support a C or C++ .NET compiler

· To make it easy to port pointer-based algorithms to C#

Always use pointers for image manipulation in .NET

Avoid using pointers in C#, unless:

· Your algorithm processes a large block of data (10MB+)

· Your algorithm processes the data sequentially

· Your algorithm reads or writes data at or very close to the current pointer address

Using pointers will give you a 25% performance gain!

Sample code

Unsafe Keyword

Use unsafe keyword to tell the compiler of code that is not managed by the GC:

unsafe { // code using pointers }

public unsafe void Method (int i) { // code using pointers }

Pointer declaration

Byte* p = null;

Generic form: Type*

Fixed keyword

The object q pinned on the heap, will not be moved by the garbage collector.

fixed (Byte* p = &q) { … }

Pointer operations

Byte b = *p;

Byte b = p[2];

p = p + 3;


· Use high-level methods to access data in unmanaged memory, to avoid unsafe code

· If you need to read or write large blocks of unmanaged data directly, then obtain a pointer to the data

· For image manipulation always use pointers, because the image data will be too large for any kind of high-level access


Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s