关于性能:使用C#类型表示度量单位

Using C# types to express units of measure

我试图通过将double包装成struct来获得我所称的度量单位系统。我有C结构,如米、秒、度等。我最初的想法是,在编译器内联之后,我将拥有与使用double相同的性能。

我的显式和隐式操作符简单而直接,编译器实际上是将它们内联的,但是使用meter和second的代码比使用double的相同代码慢10倍。

我的问题是:为什么C编译器不能使使用第二个的代码像使用Double的代码一样最佳,如果它无论如何都能输入所有的代码?

第二个定义如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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会内联它。

我检查了运行时发出的程序集操作。对于这样的代码,它们是不同的:

1
2
3
4
5
6
var x = new double();
for (var i = 0; i < 1000000; i++)
{
    x = x + 2;
    // Many other simple operator calls here
}

就像这样:

1
2
3
4
5
6
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一样快?似乎在理论上,二次和二次之间没有区别,我看到的区别的深层原因是什么?


这是我的观点,如果你不同意,请写一个评论,而不是沉默的投反对票。

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

double的情况下,实际上没有调用任何运算符。使用操作码add在堆栈中添加操作数。在您的例子中,方法op_Add被调用,加上3个struct复制到堆栈和从堆栈复制。

为了优化它,首先用class替换struct。它至少能将拷贝数量减到最少。


C编译器不内联任何东西——JIT可能会这样做,但没有义务这样做。不过,它还是应该足够快的。我可能会删除+中的隐式转换(请参阅下面的构造函数用法)-还要查看一个运算符:

1
2
3
4
5
6
7
8
9
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操作码;它几乎不起作用—因为代码调用了一些静态方法和构造函数;即使是内联的,也总是会有一些开销。