为什么重载++比仅增加值要花费更长的时间?

时间:2018-11-06 04:02:33

标签: c# operator-overloading

为什么将一个Uint(以我为例)增加10.000.000次需要约0.175秒,而将struct中的Uint递增相同的时间却需要约1.21秒?

测试已经进行了大约10次,并且在时间上几乎具有相同的结果。如果没有帮助,那就去吧。但是我想知道是什么原因造成的。时间增加是相当可观的。下面的运算符重载是所使用的确切代码:

private uint _num;        
public static Seq operator ++(Seq a)
{
    a._num++; return a;
}

我选择编辑实例本身(如果这违反了准则),而不是返回新实例,因为这会花费较长时间。

此结构会经常增加,因此我正在寻找增加处理时间的原因。

3 个答案:

答案 0 :(得分:8)

这仅仅是抖动有多聪明的问题。对于常规的局部int变量,该语句

x++;
在许多情况下,

可以简化为单机指令,因为可以注册局部变量。如果未注册,则指令序列将是加载值,对其进行递增和存储,因此只有少量指令。

但是在结构上重载++具有以下语义。假设我们在s中有结构,我们说s++。这意味着我们有效地实施了

s = S.operator++(s);

这是做什么的?

  1. s复制到本地变量位置,该位置是新的形式参数
  2. 存储所有将被被调用者覆盖的寄存器状态
  3. 执行通话指令
  4. 加载形式的值,增加它的值,存储它
  5. 将新值复制到为返回值保留的位置
  6. 执行退货指令
  7. 恢复上一个激活帧的状态

  8. 将返回的值复制到s的位置。

因此,您的快速程序正在执行步骤4。您的慢速程序正在执行步骤1至8,并且速度慢了大约八倍。紧张不安的人可能会确定这是内联的候选对象,并且摆脱了其中的一些费用,但这绝不是必需的,并且有很多原因使其选择不内联。抖动不知道此增量对您很重要。

答案 1 :(得分:2)

我认为这是因为您的Seq是一种结构(值类型),是一种增量运算符的工作方式。如您所见,public static Seq operator ++(Seq a) { ... }正在返回您的Seq结构的实例。但是,当结构按值传递时,它实际上会创建一个Seq的新实例,并将其返回,这是您的开销。

再看一个例子:

struct SeqStruct
{
    private uint _num;        
    public void Increment() => _num++;
}

// ----------------------------------

var seq = new SeqStruct();
var stopwatch = Stopwatch.StartNew();
for (int i = 0; i < 100000000; i++)
    seq.Increment();
s3.Stop();

现在,如果您测量Increment()方法的调用时间,您可能会发现它现在更接近于“纯” uint增量,并且如果您切换到 Release 构建配置,您将拥有与“纯” uint增量相同的时间(此方法为“内联”)。

另一种选择是使用class代替struct

class SeqClass
{
    private uint _num;
    public static SeqClass operator ++(SeqClass a) { a._num++; return a; }
}

现在增量也会更快。

答案 2 :(得分:0)

首先,这是要求链接绩效指标的问题:https://ericlippert.com/2012/12/17/performance-rant/这些问题属于过早的优化/并不真正重要。

原因:除了正确测量的问题外,至少还有一个函数调用Overhead。它不会影响您从裸露的指针移到多远,内部仍然有两个jump和一个从函数堆栈中进行的添加/检索。

或者至少在大多数情况下都会发生。问题是JiT可以执行并inline进行该函数调用。甚至可以部分重新编译以进行此更改。真的很难预测是否以及何时这样做。