BenchmarkDotNet: Advanced Features

Atul Sharma

In this article, we saw the basic features of BenchmarkDotNet and understood why do we need them. Here we will use this amazing tool’s advanced features and compare generation-wise memory and total memory allocation and try to understand more.

Scenario 

in this scenario, we are going to access the master data with and without the implementation of the singleton class. For the Singleton class, we will use the Lazy<T> from C# as we are just doing the creation of the class and hence it will be thread-safe.

For this scenario, we are fetching the list of Sates in the US from a text file (in your case it could be a database of any other source as well) and getting the count of states. (definitely, your scenario could be more complicated).

See also  Performance Consideration for C# Conditional Statements

To have it called from multiple consumers, I am calling it in a for loop for 5 times. (another imitation).

Let’s do it

For without the Singleton scenario, we will read it from the text file upfront. This may emulate Data Access Layer in the actual project. 

 public class DAO
    {
        public List<string> GetStates()
        {
            List<string> states = new List<string>();

            var sts = File.ReadAllLines(@"C:\Hands-On\SingletonComparison\SingletonComparison\Data\States.txt");
            foreach (var item in sts)
            {
                states.Add(item);
            }

            return states;
        }
    }

For singleton, we use this DAO class to get the data in Singleton’s class private method GetData() 

public sealed class StateDataCache

    {
        private static Lazy<StateDataCache> local = new Lazy<StateDataCache>(GetData);

        private List<string> _names = null;

        public static List<string> Names
        {
            get
            {
                return local.Value._names;
            }
        }
        private static StateDataCache GetData()
        {
            DAO ObjDAO = new DAO();
            StateDataCache cache = new StateDataCache();
            cache._names = ObjDAO.GetStates();
            Console.WriteLine("GetData Method Called");
            return cache;
        }
}


For calling both of the above, we are going to use the two methods 

 public class ComparePerfromance
    {
        public void CallNormal()
        {            
            for (int i = 0; i < 5; i++)
            {
                DAO ObjDAO = new DAO();
                int iCount = ObjDAO.GetStates().Count;
            }
        }

        public void CallSingleton()
        {
            for (int i = 0; i < 5; i++)
            {
                int iCount = StateDataCache.Names.Count;
            }

        }

    }


Additional References to use the advanced feature of BenchmarkDotNet –

From Nuget package installer – add this package BenchmarkDotNet.Diagnostics.Windows, following the standard process of adding assemblies.
Add these many attributes to the test class i.e. ComparePerfromance

    [KeepBenchmarkFiles]
    [RPlotExporter]
    [HtmlExporter]
    [NativeMemoryProfiler]
    [MemoryDiagnoser]

and call it from Main()

 BenchmarkRunner.Run<ComparePerfromance>();
 Console.ReadLine();

Here we see the final code in Program class

public class Program
    {
        public static void Main(string[] args)
        {
            BenchmarkRunner.Run<ComparePerfromance>();
            Console.ReadLine();
        }
    }

    [KeepBenchmarkFiles]
    [RPlotExporter]
    [HtmlExporter]
    [NativeMemoryProfiler]
    [MemoryDiagnoser]
    public class ComparePerfromance
    {
        [Benchmark]
        public void CallNormal()
        {
            DAO ObjDAO = new DAO();
            for (int i = 0; i < 5; i++)
            {
                int iCount = ObjDAO.GetStates().Count;
            }
        }

        [Benchmark]
        public void CallSingleton()
        {
            for (int i = 0; i < 5; i++)
            {
                int iCount = StateDataCache.Names.Count;
            }

        }

    }


Again remember these points to run this diagnostic

  1. Run Visual Studio in Administrative mode. (when your user account has limited privilege on the machine)
  2. No Method should be static
  3. The solution should be in release mode
  4. All methods should be public
See also  All About C# Immutable Classes

Analysis of the Result –After running the above program, we see the following output

BenchmarkDotNet: Advanced Features
Comparison Result


Let us try to get more insight into it

  1. CallSingleton() have more than 10000 times better performance. It depends on scenarios, for a higher no of iteration, the performance gain will be even higher.
  2. Allocated memory is memory allocation per operation. CallNormal(), the Total allocated memory is higher. Showing null for Singleton means it is insignificantly low.
  3. CallNormal(), we see Gen 0, memory allocation means, objects are created for a very short time span only. ( you can refer to DAO objects). For the actual project scenario, it will be even higher, and having more objects stuck in Gen 0 is NOT recommended. 

So here in this article, we explored the advanced features of BenchmarkDotNet with the example of with and without singleton class implementation. Hope you learn something from it for your project and code is available here for all experiments.


Happy Benchmarking !!! Happy Coding !!!

One comment

Comments are closed.