简体   繁体   English

包含原始值类型的结构是否是C#中的零成本抽象?

[英]Is a struct wrapping a primitive value type a zero cost abstraction in C#?

Sometimes I want to add more typesafety around raw doubles. 有时我想在原始双打周围增加更多类型的安全性。 One idea that comes up a lot would be adding unit information with the types. 出现的一个想法是添加类型的单位信息。 For example, 例如,

struct AngleRadians {
  public readonly double Value;
  /* Constructor, casting operator to AngleDegrees, etc omitted for brevity... */
}

In the case like above, where there is only a single field, will the JIT be able to optimize away this abstraction in all cases? 在如上所述的情况下,只有一个字段,JIT是否能够在所有情况下优化这种抽象? What situations, if any, will result in extra generated machine instructions compared to similar code using an unwrapped double? 与使用未包装的双倍的类似代码相比,什么情况(如果有的话)将导致额外生成的机器指令?

Any mention of premature optimization will be downvoted. 任何提及过早优化的内容都将被低估。 I'm interested in knowing the ground truth. 我很想知道真相。

Edit: To narrow the scope of the question, here are a couple of scenarios of particular interest... 编辑:为了缩小问题的范围,这里有几个特别感兴趣的场景......

// 1. Is the value-copy constructor zero cost?
// Is...
var angleRadians = new AngleRadians(myDouble);
// The same as...
var myDouble2 = myDouble;

// 2. Is field access zero cost?
// Is...
var myDouble2 = angleRadians.Value;
// The same as...
var myDouble2 = myDouble;

// 3. Is function passing zero cost?
// Is calling...
static void DoNaught(AngleRadians angle){}
// The same as...
static void DoNaught(double angle){}
// (disregarding inlining reducing this to a noop

These are some of the things I can think of off the top of my head. 这些是我能想到的一些问题。 Of course, an excellent language designer like @EricLippert will likely think of more scenarios. 当然,像@EricLippert这样出色的语言设计师可能会想到更多场景。 So, even if these typical use cases are zero-cost, I still think it would be good to know if there is any case where the JIT doesn't treat a struct holding one value, and the unwrapped value as equivalent, without listing each possible code snippet as it's own question 因此,即使这些典型用例是零成本,我仍然认为最好知道是否存在JIT不处理持有一个值的结构,并且将未包装的值视为等效的情况,而不列出每个可能的代码片段,因为它是自己的问题

There can be some slight and observable differences because of ABI requirements. 由于ABI要求,可能存在一些轻微且可观察到的差异。 For instance for Windows x64, a struct-wrapped float or double will be passed to a callee via an integer register, while floats and doubles are passed via XMM registers (similarly for returns). 例如,对于Windows x64,结构包装的float或double将通过整数寄存器传递给被调用者,而浮点数和双精度数通过XMM寄存器传递(类似于返回)。 At most 4 ints and 4 floats can be passed via registers. 最多4个整数和4个浮点数可以通过寄存器传递。

The actual impact of this is very context dependent. 这种影响实际上与环境有关。

If you extend your example to pass a mixture of at least 5 integer and struct-or-double args, you will run out of integer arg registers faster in the struct wrapped double case, and calls and accesses to the trailing (non-register passed) args in the callee will be slightly slower. 如果你扩展你的例子以传递至少5个整数和struct-or-double args的混合,你将在struct wrapped double case中更快地用完整数arg寄存器,并调用和访问尾部(非寄存器传递) )被叫方中的args会稍微慢一些。 But the effect can be subtle as the first callee access will usually cache the result back in a register. 但是效果可能很微妙,因为第一个被调用者访问通常会将结果缓存回寄存器中。

Likewise if you pass a mixture of at least 5 doubles and struct wrapped doubles you can fit more things in registers at a call than if you passed all args as doubles or all args as struct wrapped doubles. 同样,如果你传递至少5个双打和结构包装双打的混合,你可以在调用时在寄存器中放入更多东西,而不是将所有args作为双精度或所有args传递为struct wrapped double。 So there might be some small advantage to having some struct wrapped doubles and some non struct wrapped doubles. 因此,有一些结构包装双打和一些非结构包装双打可能有一些小优势。

So in isolation, the pure call overhead and raw access to args is lower if more args fit in registers, and that means struct wrapping some doubles helps if there are a number of other doubles, and not struct wrapping helps if there are a number of other integers. 所以在隔离的情况下,如果更多的args适合寄存器,那么纯粹的调用开销和对args的原始访问会更低,这意味着如果存在许多其他双精度数,则包装一些双精度数会有所帮助,如果存在多个结构,则不包括结构包装。其他整数。

But there are complications if either the caller and callee both computes with the values and also receives or passes them -- typically in those cases struct wrapping will end up being a bit slower as the values must be moved from an int register to the stack or (possibly) a float register. 但是如果调用者和被调用者都计算值并且还接收或传递它们,则会出现复杂情况 - 通常在这种情况下,由于必须将值从int寄存器移动到堆栈,因此结构包装最终会慢一些。 (可能)一个浮动寄存器。

Whether or not this cancels out the small potential gains at the calls depends on the relative balance of computation vs calls and how many args are passed and what types the args are, register pressure, etc. 这是否取消了调用的小潜在收益取决于计算与调用的相对平衡以及传递了多少args以及args的类型,注册压力等。

ABIs that have HFA struct passing rules tend to be better insulated from this kind of thing, as they can pass struct wrapped floats in float registers. 具有HFA结构传递规则的ABI往往更好地与这种事物隔离,因为它们可以在浮点寄存器中传递struct wrapped浮点数。

I found no significant performance difference running a billion trials of DoNaught in debug mode with optimizations turned on. 我发现,在启用优化的情况下,在调试模式下运行数十亿个DoNaught试验没有显着的性能差异。 Sometimes, double won, and sometimes, the wrapper won. 有时,双赢,有时,包装赢了。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM