Click here to Skip to main content
15,890,438 members
Articles / Programming Languages / C#
Tip/Trick

How to Simply Make a Little Benchmark to Get an Idea of ​​the Performance of the Source Code with Benchmaker .NET 4.0+

Rate me:
Please Sign up or sign in to vote.
5.00/5 (6 votes)
5 Jun 2017CPOL3 min read 7.4K   2  
In many cases, it is important to know if performance of third party library or own code is efficient or not.

Introduction

I often care about performance but with enough recoil to avoid the premature optimization (root of evil...). Indeed unless performance is a crux of business, you don't have to take so much care about it to avoid to impact the way in which you design your application. However, when you choose the architecture of your code, you can be curious and aware about how doing something more efficient.

Optimize or Not?

Don't apply a micro optimization without any benchmark unless you are very certain you gain something and sacrifice nothing else. On the other hand, don't apply an optimization that will break readability and design unless it is absolutely required.

Why Premature Optimization is the Root of Evil?

This quote is often used to get rid of optimization consideration. Indeed, a program is rarely slow because of block of code. Design is the mainly culprit of bad performance. So don't change something clean by something very ugly to gain an epsilon.

What are the Rare Cases where Micro Optimization can be Useful?

The programs that are done to perform algorithmic calculations are the main targets. It can be interesting to micro-optimize into technical code, typically in frameworks which are often massively called in an application.

Benchmaker .NET 4.0+

Benchmaker is a simple open source library available as nuget package under MIT licence:

It allows to quickly make a benchmark to get an idea of ??the performance of the source code. This is not a framework nor a full integrated util. Just a little library to help you to code your Stopwatch pattern without losing time.

I often create a console application to just test something or compare performances of code and often repeat the same pattern with Stopwatch, loop and garbage collection management. Maybe you will recognize it in this case.

Thanks to Benchmaker, I can save time when doing it, especially when I compare many things.

How to Use Benchmaker?

Benchmaker got a class called Benchmark having a constructor with one argument corresponding to a Func<Action> that is the way to create a referential treatment. Then you can add alternative treatments to compare, thanks to Benchmark.Add(Func<Action> alternative) method.

Example 1

  • Here, creation of benchmark with standard instantiation of object as referential treatment.
    C#
    var _benchmark = new Benchmark(() => new Action(() => new object()));
  • Now, we can see some alternatives like Activator.CreateInstance(...) and other way to create an object:
    C#
    //Add Activator.CreateInstance call as alternative.
    _benchmark.Add("Activator", () =>
    {
        var _type = typeof(object);
        return new Action(() => { Activator.CreateInstance(_type); });
    });
    
    //Add ConstructorInfo.Invoke as alternative
    _benchmark.Add("ConstructorInfo", () =>
    {
        var _constructor = typeof(object).GetConstructor(Type.EmptyTypes);
        var _arguments = new object[0];
        return new Action(() => { _constructor.Invoke(_arguments); });
    });
    
    //Add Lambda wrap to check overhead bring by wrap as alternative
    _benchmark.Add("Lambda", () =>
    {
        var _activate = new Func<object>(() => new object());
        return new Action(() => { _activate(); });
    });
    
    //Add Lambda Expression as alternative to compare with manual wrap
    _benchmark.Add("Expression", () =>
    {
        var _activate = Expression.Lambda<Func<object>>(Expression.New(typeof(object))).Compile();
        return new Action(() => { _activate(); });
    });
    
    //Add FormatterServices.GetUninitializedObject as alternative used in serialization
    _benchmark.Add("FormatterServices", () =>
    {
        var _type = typeof(object);
        return new Action(() => { FormatterServices.GetUninitializedObject(_type); });
    });
    
    //Add DynamicMethod as alternative to compare with expression alternative
    _benchmark.Add("DynamicMethod", () =>
    {
        var _type = typeof(object);
        var _method = new DynamicMethod(string.Empty, _type, new Type[] { _type }, _type, true);
        var _body = _method.GetILGenerator();
        _body.Emit(OpCodes.Newobj, _type.GetConstructor(Type.EmptyTypes));
        _body.Emit(OpCodes.Ret);
        var _activate = _method.CreateDelegate(typeof(Func<object>), null) as Func<object>;
        return new Action(() => { _activate(); });
    });
  • When all alternatives are included, just call Benchmark.Run(Action<string>) / Benchmark.Run() to run the benchmark.
    C#
    _benchmark.Run();

The result shows activation time, warmup time and call speed for each alternative and a final report (order by performance) is produced to compare with referential treatment. Values are rounded to gain readability.

Benchmark
    Activation : Ticks
        [none] = 30
        Activator = 20
        Generic Activator = 20
        ConstructorInfo = 180
        Lambda = 20
        Expression = 14.000
        FormatterServices = 30
        DynamicMethod = 730
    Warmup : Ticks
        [none] = 10
        Activator = 90
        Generic Activator = 620
        ConstructorInfo = 30
        Lambda = 550
        Expression = 40
        FormatterServices = 270
        DynamicMethod = 20
    Loop : iteration / second
        [1]
            Expression = 38.000.000
            Lambda = 46.000.000
            FormatterServices = 6.000.000
            ConstructorInfo = 2.400.000
            [none] = 110.000.000
            Generic Activator = 5.400.000
            DynamicMethod = 48.000.000
            Activator = 8.100.000
        [2]
            DynamicMethod = 64.000.000
            ConstructorInfo = 3.100.000
            Activator = 8.500.000
            [none] = 150.000.000
            Lambda = 61.000.000
            FormatterServices = 7.200.000
            Expression = 64.000.000
            Generic Activator = 7.000.000
        [3]
            Generic Activator = 7.900.000
            ConstructorInfo = 3.600.000
            Lambda = 74.000.000
            Expression = 75.000.000
            FormatterServices = 9.100.000
            Activator = 10.000.000
            [none] = 170.000.000
            DynamicMethod = 76.000.000
        [4]
            ConstructorInfo = 3.500.000
            Generic Activator = 8.400.000
            [none] = 190.000.000
            Activator = 11.000.000
            FormatterServices = 10.000.000
            DynamicMethod = 83.000.000
            Lambda = 79.000.000
            Expression = 83.000.000
        [5]
            ConstructorInfo = 4.100.000
            Generic Activator = 9.200.000
            DynamicMethod = 83.000.000
            Activator = 11.000.000
            Lambda = 79.000.000
            FormatterServices = 10.000.000
            Expression = 87.000.000
            [none] = 220.000.000
        [6]
            Activator = 13.000.000
            FormatterServices = 11.000.000
            [none] = 220.000.000
            ConstructorInfo = 4.600.000
            DynamicMethod = 90.000.000
            Lambda = 85.000.000
            Generic Activator = 10.000.000
            Expression = 92.000.000

 ===============================
                [none] :   100 %
 [1]     DynamicMethod :   240 %
 [2]        Expression :   240 %
 [3]            Lambda :   260 %
 [4]         Activator : 1.700 %
 [5] FormatterServices : 2.000 %
 [6] Generic Activator : 2.200 %
 [7]   ConstructorInfo : 4.900 %
 ===============================
Appuyez sur une touche pour continuer...

At this point, we can see that generic activator is not performing better than non generic activator!?

And we can see that if you have to play with reflection to create an instance of a object, prefer Activator over ConstructorInfo.Invoke for parameterless constructor at least.

This demo can be found here:

Example 2: IOC Container Comparison when Resolve Transient Instance

C#
using System;
using Benchmaker;
using Autofac;
using DryIoc;
using Ninject;
using Castle.Windsor;
using Microsoft.Practices.Unity;

namespace Puresharp.SimpleInjectorBattle
{
    public interface ICalculator
    {
    }

    [System.Composition.Export(typeof(ICalculator))]
    public class Calculator : ICalculator
    {
    }

    public class Primary
    {
    }

    public class Secondary
    {
    }
    
    static public class Program
    {
        [STAThread]
        static public void Main(string[] args)
        {
            var _benchmark = new Benchmark(() => new Action(() => new Calculator()));
            _benchmark.Add("SimpleInjector", () => 
            {
                var _container = new SimpleInjector.Container();
                _container.Register<ICalculator, Calculator>(SimpleInjector.Lifestyle.Transient);
                return () => _container.GetInstance<ICalculator>();
            });
            _benchmark.Add("Puresharp [static]", () =>
            {
                Puresharp.Composition.Container<Secondary>.Add<ICalculator>(() => new Calculator());
                return () => Puresharp.Composition.Container<Secondary>.Instance<ICalculator>();
            });
            _benchmark.Add("Puresharp [static] with optimizer", () => 
            {
                Puresharp.Composition.Container<Primary>.Add<ICalculator>(() => new Calculator());
                return () => Puresharp.Composition.Container<Primary>.Lookup<ICalculator>.Instance();
            });
            _benchmark.Add("Puresharp [instance]", () =>
            {
                var _container = new Puresharp.Composition.Container();
                _container.Add<ICalculator>(() => new Calculator());
                return () => _container.Instance<ICalculator>();
            });
            _benchmark.Add("Puresharp [instance] with optimizer", () =>
            {
                var _container = new Puresharp.Composition.Container();
                _container.Add<ICalculator>(() => new Calculator());
                return () => Puresharp.Composition.Container.Lookup<ICalculator>.Instance(_container);
            });
            _benchmark.Add("MEF", () =>
            {
                var _container = new System.Composition.Hosting.ContainerConfiguration().WithAssembly
                (typeof(ICalculator).Assembly).CreateContainer();
                return () => _container.GetExport<ICalculator>();
            });
            _benchmark.Add("Castle Windsor", () =>
            {
                var _container = new WindsorContainer();
                _container.Register(Castle.MicroKernel.Registration.Component.For
                <ICalculator>().ImplementedBy<Calculator>());
                return () => _container.Resolve<ICalculator>();
            });
            _benchmark.Add("Unity", () =>
            {
                var _container = new UnityContainer();
                _container.RegisterType<ICalculator, Calculator>();
                return () => _container.Resolve<ICalculator>();
            });
            _benchmark.Add("StuctureMap", () =>
            {
                var _container = new StructureMap.Container
                (_Builder => _Builder.For<ICalculator>().Use<Calculator>());
                return () => _container.GetInstance<ICalculator>();
            });
            _benchmark.Add("DryIoc", () => 
            {
                var _container = new DryIoc.Container();
                _container.Register<ICalculator, Calculator>();
                return () => _container.Resolve<ICalculator>();
            });
            _benchmark.Add("Autofac", () =>
            {
                var _builder = new Autofac.ContainerBuilder();
                _builder.RegisterType<Calculator>().As<ICalculator>();
                var _container = _builder.Build(Autofac.Builder.ContainerBuildOptions.None);
                return () => _container.Resolve<ICalculator>();
            });
            _benchmark.Add("Ninject", () =>
            {
                var _container = new Ninject.StandardKernel();
                _container.Bind<ICalculator>().To<Calculator>();
                return () => _container.Get<ICalculator>();
            });
            _benchmark.Add("Abioc", () => 
            {
                var _setup = new Abioc.Registration.RegistrationSetup();
                _setup.Register<ICalculator, Calculator>();
                var _container = Abioc.ContainerConstruction.Construct
                                  (_setup, typeof(ICalculator).Assembly);
                return () => _container.GetService<ICalculator>();
            });
            _benchmark.Add("Grace", () => 
            {
                var _container = new Grace.DependencyInjection.DependencyInjectionContainer();
                _container.Configure
                (c => c.Export<Calculator>().As<ICalculator>());
                return () => _container.Locate<ICalculator>();
            });
            _benchmark.Run(Console.WriteLine);
        }
    }
}

Let's see the kind of output we can extract from this benchmark:

Benchmark
    Activation : Ticks
        [none] = 60
        SimpleInjector = 190.000
        Puresharp [static] = 51.000
        Puresharp [static] with optimizer = 3.600
        Puresharp [instance] = 31.000
        Puresharp [instance] with optimizer = 2.800
        MEF = 180.000
        Castle Windsor = 480.000
        Unity = 130.000
        StuctureMap = 210.000
        DryIoc = 160.000
        Autofac = 190.000
        Ninject = 110.000
        Abioc = 8.800.000
        Grace = 98.000
    Warmup : Ticks
        [none] = 10
        SimpleInjector = 100.000
        Puresharp [static] = 370
        Puresharp [static] with optimizer = 240
        Puresharp [instance] = 440
        Puresharp [instance] with optimizer = 340
        MEF = 72.000
        Castle Windsor = 36.000
        Unity = 72.000
        StuctureMap = 69.000
        DryIoc = 49.000
        Autofac = 24.000
        Ninject = 72.000
        Abioc = 1.100
        Grace = 33.000
    Loop : iteration / second
        [1]
            Grace = 26.000.000
            DryIoc = 44.000.000
            Ninject = 95.000
            SimpleInjector = 18.000.000
            Castle Windsor = 3.300.000
            Abioc = 25.000.000
            MEF = 7.200.000
            Puresharp [static] with optimizer = 140.000.000
            StuctureMap = 1.200.000
            Puresharp [instance] with optimizer = 83.000.000
            Autofac = 1.600.000
            Puresharp [instance] = 79.000.000
            Unity = 1.000.000
            Puresharp [static] = 84.000.000
            [none] = 180.000.000
        [2]
            StuctureMap = 1.200.000
            Puresharp [static] with optimizer = 140.000.000
            Castle Windsor = 3.300.000
            [none] = 180.000.000
            MEF = 7.200.000
            Autofac = 1.600.000
            Ninject = 92.000
            Puresharp [instance] with optimizer = 87.000.000
            Abioc = 26.000.000
            SimpleInjector = 19.000.000
            Puresharp [instance] = 80.000.000
            Unity = 1.000.000
            Grace = 27.000.000
            Puresharp [static] = 85.000.000
            DryIoc = 45.000.000
        [3]
            Unity = 1.000.000
            DryIoc = 45.000.000
            Abioc = 26.000.000
            Castle Windsor = 3.300.000
            Puresharp [static] = 85.000.000
            Ninject = 98.000
            Puresharp [static] with optimizer = 140.000.000
            Puresharp [instance] = 79.000.000
            Autofac = 1.600.000
            Puresharp [instance] with optimizer = 86.000.000
            [none] = 180.000.000
            StuctureMap = 1.200.000
            Grace = 26.000.000
            MEF = 7.200.000
            SimpleInjector = 18.000.000
        [4]
            StuctureMap = 1.200.000
            Puresharp [static] with optimizer = 140.000.000
            Grace = 27.000.000
            Puresharp [instance] = 78.000.000
            Puresharp [static] = 82.000.000
            DryIoc = 43.000.000
            Puresharp [instance] with optimizer = 84.000.000
            Autofac = 1.600.000
            Ninject = 92.000
            MEF = 7.200.000
            SimpleInjector = 19.000.000
            Unity = 1.000.000
            Castle Windsor = 3.300.000
            [none] = 190.000.000
            Abioc = 26.000.000
        [5]
            Ninject = 97.000
            DryIoc = 44.000.000
            Puresharp [instance] with optimizer = 84.000.000
            StuctureMap = 1.200.000
            SimpleInjector = 19.000.000
            [none] = 180.000.000
            Puresharp [static] = 84.000.000
            Unity = 1.000.000
            Abioc = 25.000.000
            Autofac = 1.600.000
            Grace = 27.000.000
            Puresharp [instance] = 79.000.000
            Castle Windsor = 3.300.000
            Puresharp [static] with optimizer = 140.000.000
            MEF = 7.200.000
        [6]
            Puresharp [static] = 84.000.000
            StuctureMap = 1.200.000
            Ninject = 92.000
            SimpleInjector = 19.000.000
            DryIoc = 45.000.000
            [none] = 180.000.000
            Abioc = 25.000.000
            Puresharp [instance] with optimizer = 86.000.000
            MEF = 7.200.000
            Grace = 26.000.000
            Puresharp [instance] = 78.000.000
            Unity = 1.000.000
            Puresharp [static] with optimizer = 140.000.000
            Castle Windsor = 3.300.000
            Autofac = 1.600.000

 ====================================================
                                   [none] :     100 %
 [1]    Puresharp [static] with optimizer :     120 %
 [2]  Puresharp [instance] with optimizer :     210 %
 [3]                   Puresharp [static] :     220 %
 [4]                 Puresharp [instance] :     230 %
 [5]                               DryIoc :     410 %
 [6]                                Grace :     690 %
 [7]                                Abioc :     720 %
 [8]                       SimpleInjector :     980 %
 [9]                                  MEF :   2.500 %
 [10]                      Castle Windsor :   5.600 %
 [11]                             Autofac :  11.000 %
 [12]                         StuctureMap :  14.000 %
 [13]                               Unity :  17.000 %
 [14]                             Ninject : 190.000 %
 ====================================================
Appuyez sur une touche pour continuer...

We can easily see that performance of different IOC containers is clearly not the same but reassure you, the performances of an IOC container are not as critical as it was read in many articles on the subject. I find that they are all reasonable except NInject. I am even pleasantly surprised by the performance of MEF (here MEF2 to be exact). I will not talk about IOC container here but maybe in another tip.

An example can be download here:

Conclusion

Benchmarks can be interesting to reinforce our choices or simply to satisfy curiosity. I find it is often cumbersome to write manually. Most of the frameworks want to do too much and bring a lot of features to the detriment of simplicity. Benchmaker is a nice little library to get an idea without investing too much because it auto-scales to bench something relatively right without letting you wait so long to see the result.

License

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


Written By
Architect
France France
After working in electronics, I fall in love with software development which is a real passion for me, particulary architecture concerns for productivity.

Comments and Discussions

 
-- There are no messages in this forum --