Dear reader, let me present Part-2 of 5 tips to improve your C#. If you did not read my first two series then I recommend you go to the following links to get a complete idea of C# code performance:
This article however is totally independent of the previous two. If you enjoy them then I hope you will enjoy this too. In my previous article I was showing which approach is faster and comparing execution speed of code. In this article I would like to show memory consumption of various code snippets. To show a memory map and allocation graph I have used the CLR profiler 32bit version and again as usual I am using 4GB of RAM and a Core i3 CPU with Windows platform. The memory consumption or allocation graph may vary depending on the running processes of your system. So if you get a different output or behavior of the code then please share your experience with us.
Let's start our journey of "5 tips to improve your C# code: Part-3".
Tip 1: StringBuilder consumes less memory than String
In my previous article I have shown how slow string is in a scenario of long concatenation operations. And here we will see a memory allocation graph of String and StringBuilder. Let me show that in action. The following is my code to do same operation with both a string and a StringBuilder.
- using System;
- using System.Collections.Generic;
- using System.Linq;
- using System.Text;
- using System.Diagnostics;
- using System.IO;
- using System.Net;
- using System.Net.NetworkInformation;
- using System.Threading;
- using System.Globalization;
- using System.Data.SqlClient;
- namespace Test1
- {
- public class Test1
- {
- string Name ;
- public void Process()
- {
- Name = Name + "A";
- }
- }
- public class Test2
- {
- StringBuilder sb = new StringBuilder();
- public void Process()
- {
- sb.Append("A");
- }
- }
- class Program
- {
- static void Main(string[] args)
- {
- Test1 t = new Test1();
- t.Process();
- Test2 t1 = new Test2();
- t1.Process();
- }
- }
- }
And here is the memory allocation graph from execution of the code.
Here from the main function we are calling the two functions Process(); though they both have the same name they belong to different classes and Test1.Process is handling string data whereas Test2.Process() is handling string builder data. And in the allocation graph we can see the String handling function consumes 94% resource of the Main () function whereas Process() in the Test2 class that deals with StringBuilder only consumes .21 % of the resources of the Main() function.
So, in a single line the conclusion is "Always use StringBuilder when you want to concatenate strings many times".
Tip 2: If possible use a static function
Yes, if possible try to implement a static function because static objects (both function and data) does not belong to any object of a particular class. It's common to all. So if you do not create an object then there is no question of memory consumption. In the following I am showing one example of a static function and static class. And have a look at the IL code.
- using System;
- using System.Collections.Generic;
- using System.Linq;
- using System.Text;
- using System.Diagnostics;
- using System.IO;
- using System.Net;
- using System.Net.NetworkInformation;
- using System.Threading;
- using System.Globalization;
- using System.Data.SqlClient;
- namespace Test1
- {
- public static class mySclass
- {
- public static void Print()
- {
- Console.Write("Hello");
- }
- }
- public class myNclass
- {
- public static void Print()
- {
- Console.Write("Hello");
- }
- }
- class Program
- {
- static void Main(string[] args)
- {
- for (int i = 0; i < 1000; i++)
- {
- mySclass.Print();
- myNclass.Print();
- }
- }
- }
- }
The IL code is in the left hand side and in the right hand the top few memory consuming classes, taken by the CLR profiler. I cannot show the full screenshot of the CLR profiler due to space consumption. But believe me (Ha..Ha) there is no memory allocation of a static class or function.
So, in a single line the conclusion is "If possible try to create a static function and invoke with the class name rather than invoking the general function by object name".
Tip 3: String format VS String concatenation
In the first point I was showing how a string consumes more resources than a StringBuilder. In this point I will compare formatted output with string concatenation. In the first function I am using a format specification to print formatted output (basically I am concatenating a string). And in another function I am using the (+) operator to concatenate a string, as in the following:
- using System;
- using System.Collections.Generic;
- using System.Linq;
- using System.Text;
- using System.Diagnostics;
- using System.IO;
- using System.Net;
- using System.Net.NetworkInformation;
- using System.Threading;
- using System.Globalization;
- using System.Data.SqlClient;
- namespace Test1
- {
- class Test
- {
- public void Format()
- {
- int a = 100;
- Console.WriteLine("{0}AND{1}", a, a);
- }
- public void Concatination()
- {
- int a = 100;
- Console.WriteLine(a + "AND" +a );
- }
- }
- class Program
- {
- static void Main(string[] args)
- {
- Test t = new Test();
- t.Format();
- t.Concatination();
- Console.ReadLine();
- }
- }
- }
And in memory allocation we will see:
That function that was printing a string using format is consuming 57 % of the resources and the function that simply concatenates two strings consumes 30 % resources of the main function. So we can clearly see if we use string concatenation rather than output formatting we can save our system resources.
Tip 4: Which class consumes maximum resources in an empty program?
First of all this point does not recommend any best practice technique. I just want to show that if we run one empty program (with just a Main() function in it) then how much memory is allocated? The following is my very simple program.
- using System;
- using System.Collections.Generic;
- using System.Linq;
- using System.Text;
- namespace Test1
- {
- class Program
- {
- static void Main(string[] args)
- {
- }
- }
- }
Yes, I did not write anything in this program. Let's look at the memory map.
Here I am showing top six resource-consuming classes relevant to the scenario of when we run an empty program. It's clearly visible that the String class is taking the most resources (25 % of the whole). Now for a question. In a program that we never use a string, why does the string class consume the most resources? If we look at the call graph of this program then we will see in the main function many internal functions are being called and most of them are taking an argument as a string and to generate those arguments the CLR is usually using the String class. If you have different opinion please use comment box as in the following.
Tip 5: Implement a using block to manage memory
It's a best practice to always implement a using block to manage resources. And practically we can prove that a using block consumes less memory than without a using block statement. We know that if we implement a using block out code size might be larger because the using block internally creates a try catch in IL code but once it is implemented in IL code during runtime it efficiently handles system memory. To demonstrate this I have written a simple program as in the following.
- using System;
- using System.Collections.Generic;
- using System.Linq;
- using System.Text;
- using System.Diagnostics;
- using System.IO;
- using System.Net;
- using System.Net.NetworkInformation;
- using System.Threading;
- using System.Globalization;
- using System.Data.SqlClient;
- namespace Test1
- {
- class Test
- {
- public void Test1()
- {
- StreamWriter wr = new StreamWriter(@"D:\text.txt");
- }
- public void Test2()
- {
- using (StreamWriter wr = new StreamWriter(@"D:\abc.txt"))
- {
- }
- }
- }
- class Program
- {
- static void Main(string[] args)
- {
- Test t = new Test();
- t.Test1();
- t.Test2();
- }
- }
- }
And in the output section I have combined three output screens.
In the allocation graph we see that the using block is consuming less resources than without the using block because if we implement a using block, the program can manage memory efficiently.