Taner Şahin

TANER ŞAHİN

TANER ŞAHİN

profile-pic

C# Performance Tricks

1.01.0001

Introduction


In this article, I will talk about some performance tips and tricks in c# to improve code performance.
 

Waiting synchronously on asynchronous code

 

Don’t wait synchronously for non-completed tasks. Like Task.Result, Task.WaitTask.WaitAll, Task.WaitAny. Any synchronous dependency between two thread pool threads is susceptible to cause thread pool starvation.


Don't use Async void
 

Don't use async void. An exception thrown in an async void method is propagated to the synchronization context and might end up crashing the application. If you can’t return a task in your method move the async code to another method and call it from there


Example
 

 interface IMyInterface
        {
            void DoSomething();
        }

        class Implementation : IMyInterface
        {
            public void DoSomething()

            {
                // This method can't return a Task,
                // delegate the async code to another method
                _ = DoSomethingAsync();
            }
            private async Task DoSomethingAsync()
            {
                await Task.Delay(100);
            }
        }
 

Avoid async when possible


Example

 public async Task CallAsync()
        {
            var client = new Client();
            return await client.GetAsync();
        }

The code is semantically correct there is no problem with it but the async keyword is not needed here and can have significant overhead in hot paths. Try to remove it if it's possible
But you can't use that trick if your code is wrapped in blocks like try/catch or using.

public async Task Correct()
        {
            using (var client = new Client())
            {
                return await client.GetAsync();
            }
        }

        public Task Incorrect()
        {
            using (var client = new Client())
            {
                return client.GetAsync();
            }
        }

In the incorrect version, since the task isn’t awaited inside of the using block, the client might be disposed of before the GetAsync call completes.
 

Converting enums to string


Calling Enum.ToString in .net is very costly, as reflection is used internally for the conversion and calling a virtual method on struct causes boxing. As much as possible, this should be avoided.

You can also create const string class for that like following

 public enum Numbers
        {
            One,
            Two,
            Three,
            Four
        }
        public static class Numbers
        {
            public const string One = "One";
            public const string Two = "Two";
            public const string Three = "Three";
            public const string Four = "Four";
        }
 

Enum comparisons


When using enums as flags, it may be tempting to use the Enum.HasFlag method:

  [Flags]
        public enum Options
        {
            Option1 = 1,
            Option2 = 2,
        }

        private Options _option;
        public bool IsOption2Enabled()
        {
            return _option.HasFlag(Options.Option2);
        }

This code causes two boxing allocations: one to convert Options.Option2 to Enum, and another one for the HasFlag virtual call on a struct. This makes this code expensive. Instead, you should give up readability and use binary operators:

 public bool IsOption2Enabled()
        {
            return (_option & Options.Option2) == Options.Option2;
        }
 

CancellationToken subscriptions are always inlined

 

Whenever you cancel a CancellationTokenSource, all subscriptions will be executed inside of the current thread. This can lead to unplanned pauses or even deadlocks.

 var  cancelToken = new CancellationTokenSource();
        cancelToken.Token.Register(() => Thread.Sleep(5000));
        cancelToken.Cancel(); // This call will block during 5 seconds
 

Task.Run / Task.Factory.StartNew

 

If you don't have a reason to use Task.Factory.StartNew, always use Task.Run to start a background task. Task.Run uses safer defaults, and more importantly, it automatically unwraps the returned task, which can prevent subtle errors with async methods.

Example

   class Program
        {
            public static async Task ProcessAsync()
            {
                await Task.Delay(2000);
                Console.WriteLine("Processing done");
            }

            static async Task Main(string[] args)
            {
                await Task.Factory.StartNew(ProcessAsync);
                Console.WriteLine("End of program");
                Console.ReadLine();
            }
        }

Despite the appearances, “End of program” will be displayed before “Processing done”. This is because Task.Factory.StartNew is going to return a Task<Task>, and the code only waits on the outer task. Correct code would be either await

Task.Factory.StartNew(ProcessAsync).Unwrap()
or await Task.Run(ProcessAsync).
 There are only three legitimate use-cases for Task.Factory.StartNew:

  •  Starting a task on a different scheduler
  •  Executing the task on a dedicated thread (using TaskCreationOptions.LongRunning)
  •  Queuing the task on the thread pool global queue (using TaskCreationOptions.PreferFairness)


Memory locality matters


Let’s assume we have an array of arrays. Effectively it’s a table, 3000×3000 in size. We want to count how many slots have a value greater than zero in them.
Question – which of these two is faster?


First One
for (int i = 0; i < _map.Length; i++)
{
        for (int n = 0; n < _map.Length; n++)
        {
                if (_map[i][n] > 0)
                {
                   result++;
                }
        }
}

Second One
for (int i = 0; i < _map.Length; i++)
{
        for (int n = 0; n < _map.Length; n++)
        {
                if (_map[n][i] > 0)
                {
                  result++;
                }
        }
}
Answer? The first one. How much so? In my tests, I got about an 8x performance improvement on this loop!
Notice the difference? It’s the order that we’re walking this array of arrays ([i][n] vs. [n][i]). Memory locality does indeed matter in .NET even though we’re well abstracted from managing memory ourselves.
n my case, this method was being called millions of times (hundreds of millions of times to be exact) and therefore any performance I could squeeze out of this resulted in a sizeable win. Again, thanks to my ever-handy profiler for making sure I was focused on the right place!


Relieve the pressure on the garbage collector


C#/.NET features garbage collection. Garbage collection is the process that determines which objects are currently obsolete and removing them to free space in memory. What that means is that in C#, unlike in languages like C++, you don’t have to manually take care of the removal of objects that are no longer useful, in order to claim their space in memory. Instead, the garbage collector (GC) handles all of that, so you don’t have to.

The problem is that there’s no free lunch. The collection process itself causes a performance penalty, so you don’t really want the GC to collect all the time. So how do you avoid that?

There are many useful techniques to avoid putting too much pressure on the GC. Here, I’ll focus on a single tip: avoid unnecessary allocations. What that means is to avoid things like this:
List<Product> products = new List<Product>();
products = productRepo.All();
The first line creates an instance of the list that’s completely useless since the very next line returns another instance and assign its reference to the variable. Now imagine the two lines above are inside a loop that executes thousands of times? The code above might look like a silly example, but I’ve seen code like this in production—and not just a single time. Don’t focus on the example itself but on the general advice. Don’t create objects unless they’re really needed. Due to the way the GC works in .NET (it’s a generational GC process), newer objects are more likely to be collected than old ones. That means that the creation of many new, short-lived objects might trigger the GC to run.
 

Always use Stringbuilder for String concatenation operations


This point is very crucial for developers. Please use StringBuilder in place of String when you are making heavy string concatenation operations. To demonstrate how it impacts on code performance I have prepared the following example code. I am doing a string concatenation operation 500 times within the for loop.


public classTest
{
public staticstring Name { get;set; }
public staticString surname;
}
class Program
{
static void Main(string[] args)
{
string First = "A";
StringBuilder sb = new StringBuilder("A");
Stopwatch st = new Stopwatch();
st.Start();
for (int i = 0; i < 500; i++)
{
First = First + "A";
}
st.Stop();
Console.WriteLine("Using String :-" + st.ElapsedTicks);
st.Restart();
for (int i = 0; i < 500; i++)
{
sb.Append("A");
}
st.Stop();
Console.WriteLine("Using Stringbuilder :-" + st.ElapsedTicks);
Console.ReadLine(); } }

// Using String : -463
// Using StringBuilder: -24

 

Use a profiler

There are many great .NET profilers but  I personally use the dotTrace profiler from the Jet Brains team. If there's a slow part in your program these profilers will help you to find it. So it is better to use a profiler just in case.




#c# #programming #.net

Share

Post Comments

Leave A Reply