【问题标题】:Using C# types to express units of measure使用 C# 类型表示度量单位
【发布时间】:2011-05-07 21:53:23
【问题描述】:

我试图通过将 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 之间似乎没有理论上的区别,我看到的区别的深层原因是什么?

【问题讨论】:

  • 这是implicit+ 运算符吗?
  • 我知道您使用的是 C#,但您考虑过 F# 吗?它内置了静态单元检查您似乎正在寻找的东西。见stackoverflow.com/questions/40845/…
  • 与此非常相似:stackoverflow.com/questions/3995920/… 根据我的经验,编译器在优化结构方面相对较好。但是您需要编译为release 并且没有附加调试器
  • 我很久以前做过这样的事情来将“宽度”与“高度”分开,因为我们有很多方法可以将它们按不同的顺序排列。 int 和类型之间存在隐式转换,但它们之间没有。它适用于给定的项目。

标签: c# performance struct operator-keyword


【解决方案1】:

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

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

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

要优化它,首先将struct 替换为class。它至少会减少副本的数量。

【讨论】:

  • ...结构体会给你带来麻烦。
  • 为什么要在这里使用类而不是结构?如果他使用class,他的方法(大量的运算符和类型)的问题不会改变,但性能可能会下降很多。
  • @CodeInChaos 我从不使用struct。我真的认为它应该只与互操作一起使用。 “性能可能会下降很多”为什么?请解释一下。
  • @Andrey:创建新的堆对象实例相对昂贵。如果每次计算都需要构建一个新的堆对象实例,那么每个计算都会增加该成本。不仅值类型实例的创建成本更低,而且为有效使用此类类型而编写的代码可以重用实例,从而完全避免了此类分配的需要。
  • @supercat 为什么每次计算都涉及创建实例?此外,最优秀的编程人员建议将structs 设置为不可变的,因此不要重复使用。
【解决方案2】:

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 操作码;它几乎没有工作-因为您的代码正在调用一些静态方法和构造函数;总是会有一些开销,即使是内联的。

【讨论】:

  • 我确实将int 包装到一个结构中(实现定点),当抖动内联代码时(IMO 它应该更积极地内联代码)它产生了完美的汇编代码。因此,如果内联,则可能根本没有开销。
猜你喜欢
  • 2011-04-17
  • 2018-09-29
  • 1970-01-01
  • 2016-10-27
  • 1970-01-01
  • 2010-09-25
  • 2013-06-27
  • 2012-05-06
  • 2010-12-26
相关资源
最近更新 更多