11

我试图通过将 double 包装到结构中来获得我所谓的测量单位系统。我有 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
}

更新:

我没有问 struct 是否适合这里。确实如此。

我没有问代码是否会被内联。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 和 Second 之间似乎没有理论上的区别,我看到的差异的深层原因是什么?

4

2 回答 2

4

这是我的观点,如果您不同意,请写评论,而不是默默地反对。

C# 编译器不会内联它。JIT 编译器可能,但这对我们来说是不确定的,因为 JITer 的行为并不简单。

如果double没有实际调用运算符。使用 opcode 将操作数添加到堆栈中add。在您的情况下,方法op_Add被调用加上三个struct复制到堆栈和从堆栈复制。

要优化它,首先要替换structclass. 它至少会减少副本的数量。

于 2010-11-10T14:11:51.403 回答
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 操作码;它几乎不起作用- 因为您的代码正在调用一些静态方法和构造函数;总是会有一些开销,即使是内联的。

于 2010-11-10T14:07:30.670 回答