我正在阅读.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)