使用 C# 类型来表达度量单位
我试图通过将 double 包装到结构中来获得我所说的测量单位系统。我有 C# 结构,如米、秒、度等。我最初的想法是,在编译器内联所有内容后,我将获得与使用 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 一样快? double 和 Second 之间似乎没有理论上的区别,我看到的差异的深层原因是什么?
I'm trying to get what I call measurement units system by wrapping double into struct. I have C# structures like Meter, Second, Degree, etc. My original idea was that after compiler is inlined everything I would have a performance the same as if double were used.
My explicit and implicit operators are simple and straightforward, and compiler does actually inline them, yet the code with Meter and Second is 10 times slower than the same code using double.
My question is being: why cannot C# compiler make the code using Second as optimal as the code using double if it inlines everything anyway?
Second is defined as following:
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
}
Update:
I didn't ask if struct fits here. It does.
I didn't ask if code is going to be inlined. JIT does inline it.
I checked assembly operations emitted in runtime. They were different for code like this:
var x = new double();
for (var i = 0; i < 1000000; i++)
{
x = x + 2;
// Many other simple operator calls here
}
and like this:
var x = new Second();
for (var i = 0; i < 1000000; i++)
{
x = x + 2;
// Many other simple operator calls here
}
There were no call instructions in disassembly, so operations were in fact inlined. Yet the difference is significant. Performance tests show that using Second is like 10 times slower than using double.
So my questions are (attention!): why is JIT generated IA64 code is different for the cases above? What can be done to make struct run as fast as double? It seems there no theoretical difference between double and Second, what is the deep reason of difference I saw?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
这是我的观点,如果你不同意,请写评论,而不是默默地投反对票。
C# 编译器不会内联它。 JIT 编译器可能会这样做,但这对我们来说是不确定的,因为 JITer 的行为并不简单。
对于
double
,实际上不会调用任何运算符。使用操作码add
将操作数添加到堆栈中。在您的情况下,将调用方法op_Add
以及三个struct
复制到堆栈和从堆栈复制。要优化它,请从用
class
替换struct
开始。它至少会最大限度地减少副本数量。This is my opinion, please write a comment if you disagree, instead of silent downvoting.
C# Compiler doesn't inline it. JIT compiler might, but this is indeterministic for us, because JITer's behavior is not straightforward.
In case of
double
no operators are actually invoked. Operands are added right in stack using opcodeadd
. In your case methodop_Add
is invoked plus threestruct
copying to and from stack.To optimize it start with replacing
struct
withclass
. It will at least minimize amount of copies.C# 编译器不会内联任何东西 - JIT 可能会这样做,但没有义务这样做。不过它应该仍然足够快。不过,我可能会删除
+
中的隐式转换(请参阅下面的构造函数用法) - 还要查看另一个运算符:JIT 内联仅限于特定场景。这段代码会让他们满意吗?很难说 - 但对于大多数情况来说,它应该可以工作并且工作足够快。
+
的问题是有一个用于添加双精度数的 IL 操作码;它几乎没有工作 - 因为你的代码正在调用一些静态方法和构造函数;即使内联,总会有一些开销。The C# compiler doesn't inline anything - the JIT might do that, but isn't obliged to. It should still be plenty fast though. I would probably remove the implicit conversion in the
+
though (see the constructor usage below) - one more operator to look through:JIT inlining is limited to specific scenarios. Will this code satisfy them? Hard to tell - but it should work and work fast enough for most scenarios. The problem with
+
is that there is an IL opcode for adding doubles; it does almost no work - where-as your code is calling a few static methods and a constructor; there is always going to be some overhead, even when inlined.