繁体   English   中英

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

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

有时我想在原始双打周围增加更多类型的安全性。 出现的一个想法是添加类型的单位信息。 例如,

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

在如上所述的情况下,只有一个字段,JIT是否能够在所有情况下优化这种抽象? 与使用未包装的双倍的类似代码相比,什么情况(如果有的话)将导致额外生成的机器指令?

任何提及过早优化的内容都将被低估。 我很想知道真相。

编辑:为了缩小问题的范围,这里有几个特别感兴趣的场景......

// 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

这些是我能想到的一些问题。 当然,像@EricLippert这样出色的语言设计师可能会想到更多场景。 因此,即使这些典型用例是零成本,我仍然认为最好知道是否存在JIT不处理持有一个值的结构,并且将未包装的值视为等效的情况,而不列出每个可能的代码片段,因为它是自己的问题

由于ABI要求,可能存在一些轻微且可观察到的差异。 例如,对于Windows x64,结构包装的float或double将通过整数寄存器传递给被调用者,而浮点数和双精度数通过XMM寄存器传递(类似于返回)。 最多4个整数和4个浮点数可以通过寄存器传递。

这种影响实际上与环境有关。

如果你扩展你的例子以传递至少5个整数和struct-or-double args的混合,你将在struct wrapped double case中更快地用完整数arg寄存器,并调用和访问尾部(非寄存器传递) )被叫方中的args会稍微慢一些。 但是效果可能很微妙,因为第一个被调用者访问通常会将结果缓存回寄存器中。

同样,如果你传递至少5个双打和结构包装双打的混合,你可以在调用时在寄存器中放入更多东西,而不是将所有args作为双精度或所有args传递为struct wrapped double。 因此,有一些结构包装双打和一些非结构包装双打可能有一些小优势。

所以在隔离的情况下,如果更多的args适合寄存器,那么纯粹的调用开销和对args的原始访问会更低,这意味着如果存在许多其他双精度数,则包装一些双精度数会有所帮助,如果存在多个结构,则不包括结构包装。其他整数。

但是如果调用者和被调用者都计算值并且还接收或传递它们,则会出现复杂情况 - 通常在这种情况下,由于必须将值从int寄存器移动到堆栈,因此结构包装最终会慢一些。 (可能)一个浮动寄存器。

这是否取消了调用的小潜在收益取决于计算与调用的相对平衡以及传递了多少args以及args的类型,注册压力等。

具有HFA结构传递规则的ABI往往更好地与这种事物隔离,因为它们可以在浮点寄存器中传递struct wrapped浮点数。

我发现,在启用优化的情况下,在调试模式下运行数十亿个DoNaught试验没有显着的性能差异。 有时,双赢,有时,包装赢了。

暂无
暂无

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

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