我试图通过将double包装到struct中来获得我称之为测量单位系统的东西。我有C#结构,如Meter,Second,Degree等。我最初的想法是,在编译器内联所有内容后,我的性能与使用double时相同。
我的显式和隐式运算符简单明了,编译器确实内联它们,但是Meter和Second的代码比使用double的相同代码慢10倍。
我的问题是:为什么C#编译器不能使用Second作为使用double的代码的最佳代码,如果它无论如何都要内联?
第二个定义如下:
struct Second
{
double _value; // no more fields.
public static Second operator + (Second left, Second right)
{
return left._value + right._value;
}
public static implicit Second operator (double value)
{
// This seems to be faster than having constructor :)
return new Second { _value = value };
}
// plenty of similar operators
}
更新
我没有问过结构是否适合这里。确实如此。
我没有询问代码是否会被内联。 JIT会内联它。
我检查了运行时发出的汇编操作。对于像这样的代码,它们是不同的:
var x = new double();
for (var i = 0; i < 1000000; i++)
{
x = x + 2;
// Many other simple operator calls here
}
并且像这样:
var x = new Second();
for (var i = 0; i < 1000000; i++)
{
x = x + 2;
// Many other simple operator calls here
}
反汇编中没有调用指令,因此操作实际上是内联的。然而,差异很大。性能测试表明,使用Second比使用double要慢10倍。
所以我的问题是(注意!):为什么JIT生成的IA64代码对于上述情况有所不同?如何使struct运行速度与double一样快?似乎双重和第二之间没有理论上的区别,我看到差异的深层原因是什么?
答案 0 :(得分:4)
这是我的意见,如果你不同意,请写下评论,而不是沉默的downvoting。
C#编译器没有内联它。 JIT编译器可能,但这对我们来说是不确定的,因为JITer的行为并不简单。
如果double
没有实际调用任何运算符。使用操作码add
将操作数添加到堆栈中。在您的情况下,调用方法op_Add
加上三个struct
复制到堆栈。
要优化它,请先将struct
替换为class
。它至少可以减少副本量。
答案 1 :(得分:1)
C#编译器没有内联任何 - JIT 可能这样做,但不必。它应该仍然快速充足。我可能会删除+
中的隐式转换(请参阅下面的构造函数用法) - 另外一个要查看的运算符:
private readonly double _value;
public double Value { get { return _value; } }
public Second(double value) { this._value = value; }
public static Second operator +(Second left, Second right) {
return new Second(left._value + right._value);
}
public static implicit operator Second(double value) {
return new Second(value);
}
JIT内联仅限于特定场景。这段代码会满足他们吗?很难说 - 但是对于大多数情况,它应该工作并且工作得足够快。 +
的问题是有一个用于添加双精度的IL操作码;它几乎没有工作 - 你的代码在哪里调用一些静态方法和一个构造函数;即使在内联时,总会有一些开销。