为什么C#通用程序比特定类型的程序手专用变体表现更好?

vrn*_*mar 6 .net c# generics

我正在阅读.NET公共语言运行时的泛型设计和实现.在性能部分,它表示通用代码与手专用版本一样或更高效.

我为此创建了一个基准测试,并使用.NET基准测试库验证了它的简单堆栈.

字符串和双精度类型的结果

类型 - 双

                                  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 |
Run Code Online (Sandbox Code Playgroud)

类型 - 字符串

                                  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 |
Run Code Online (Sandbox Code Playgroud)

我非常惊讶通用代码优于我的手写代码.我试着查看为这两种情况生成的IL代码,但我无法弄清楚任何有趣的东西.唯一的主要差异是泛型使用的ldelem !0/*T*/是专门的(用于双重)代码ldelm.r8.

我错过了在运行时发生的一些优化吗?

编辑

即使我已经尝试过使用WinDbg获取两者的jitted汇编代码,也无法找出其中的任何差异.

// 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>();
}
Run Code Online (Sandbox Code Playgroud)