Why does a C # general program work better than a specialized version of a program for a particular type?

I read the Design and Implementation of Generics for the .NET Common Language Runtime . The Efficiency section says that generic code is more or less efficient than the special hand version.

I created benchmarking for this and tested it for a simple stack using the .NET testing library.

Results for both string and double type

Type - Double

Method | Mean | Error | StdDev | ---------------------------------------- |---------:|----------:|----------:| GenericStackPushAndPopForTypeDouble | 5.038 ns | 0.0522 ns | 0.0489 ns | ObjectStackPushAndPopForTypeDouble | 7.619 ns | 0.0842 ns | 0.0787 ns | HandWrittenStackPushAndPopForTypeDouble | 5.722 ns | 0.0594 ns | 0.0555 ns | 

Type - String

  Method | Mean | Error | StdDev | ---------------------------------------- |---------:|----------:|----------:| GenericStackPushAndPopForTypeString | 3.817 ns | 0.0103 ns | 0.0080 ns | ObjectStackPushAndPopForTypeString | 4.764 ns | 0.0345 ns | 0.0322 ns | HandWrittenStackPushAndPopForTypeString | 4.099 ns | 0.0298 ns | 0.0249 ns | 

I am really surprised that the generic code is superior to my handwritten code. I tried to learn the IL code created for both cases, but I could not understand anything interesting. The only major difference is generics, using ldelem !0/*T*/ , where ldelm.r8 used as specialized (for double) code.

Am I missing some runtime optimizations?

Edit

Even I tried to use WinDbg to get jitted assembly code for both and could not find any difference there.

code

 // Generic stack public class GenericStack<T> { private int _size; private T[] _stack; public GenericStack() { _size = 0; _stack = new T[10]; } public void Push(T value) { if (_size >= _stack.Length) { var newStack = new T [2*_size]; Array.Copy(_stack, newStack, _size); _stack = newStack; } _stack[_size++] = value; } public T Pop() { return _stack[--_size]; } } // Stack using object public class ObjectStack { private int _size; private object[] _stack; public ObjectStack() { _size = 0; _stack = new object[10]; } public void Push(object value) { if (_size >= _stack.Length) { var newStack = new object[2*_size]; Array.Copy(_stack, newStack, _size); _stack = newStack; } _stack[_size++] = value; } public object Pop() { return _stack[--_size]; } } // Hand specialized for double type stack public class HandWrittenDoubleStack { private int _size; private double[] _stack; public HandWrittenDoubleStack() { _size = 0; _stack = new double[10]; } public void Push(double value) { if (_size >= _stack.Length) { var newStack = new double[2 * _size]; Array.Copy(_stack, newStack, _size); _stack = newStack; } _stack[_size++] = value; } public double Pop() { return _stack[--_size]; } } // Hand specialized for string type stack public class HandWrittenStringStack { private int _size; private string[] _stack; public HandWrittenStringStack() { _size = 0; _stack = new string[10]; } public void Push(string value) { if (_size >= _stack.Length) { var newStack = new string[2 * _size]; Array.Copy(_stack, newStack, _size); _stack = newStack; } _stack[_size++] = value; } public string Pop() { return _stack[--_size]; } } // Benchmarking code [ClrJob] public class BenchMarkGenericsWithDouble { private readonly double data; private GenericStack<double> _genericStack; private ObjectStack _objectStack; private HandWrittenDoubleStack _handWrittenDoubleStack; public BenchMarkGenericsWithDouble() { _genericStack = new GenericStack<double>(); _objectStack = new ObjectStack(); _handWrittenDoubleStack = new HandWrittenDoubleStack(); data = new Random(13).NextDouble(); } [Benchmark] public double GenericStackPushAndPopForTypeDouble() { _genericStack.Push(data); return _genericStack.Pop(); } [Benchmark] public object ObjectStackPushAndPopForTypeDouble() { _objectStack.Push(data); return _objectStack.Pop(); } [Benchmark] public double HandWrittenStackPushAndPopForTypeDouble() { _handWrittenDoubleStack.Push(data); return _handWrittenDoubleStack.Pop(); } } [ClrJob] public class BenchMarkGenericsWithString { private readonly string stringData; private GenericStack<string> _genericStack; private ObjectStack _objectStack; private HandWrittenStringStack _handWrittenStringStack; public BenchMarkGenericsWithString() { _genericStack = new GenericStack<string>(); _objectStack = new ObjectStack(); _handWrittenStringStack = new HandWrittenStringStack(); stringData = "asdfasf"; } [Benchmark] public string GenericStackPushAndPopForTypeString() { _genericStack.Push(stringData); return _genericStack.Pop(); } [Benchmark] public object ObjectStackPushAndPopForTypeString() { _objectStack.Push(stringData); return _objectStack.Pop(); } [Benchmark] public string HandWrittenStackPushAndPopForTypeString() { _handWrittenStringStack.Push(stringData); return _handWrittenStringStack.Pop(); } } // Main method static void Main(string[] args) { var summaryDouble = BenchmarkRunner.Run<BenchMarkGenericsWithDouble>(); var summaryString = BenchmarkRunner.Run<BenchMarkGenericsWithString>(); } 
+5
source share

Source: https://habr.com/ru/post/1275208/


All Articles