为什么将一个Uint(以我为例)增加10.000.000次需要约0.175秒,而将struct中的Uint递增相同的时间却需要约1.21秒?
测试已经进行了大约10次,并且在时间上几乎具有相同的结果。如果没有帮助,那就去吧。但是我想知道是什么原因造成的。时间增加是相当可观的。下面的运算符重载是所使用的确切代码:
private uint _num;
public static Seq operator ++(Seq a)
{
a._num++; return a;
}
我选择编辑实例本身(如果这违反了准则),而不是返回新实例,因为这会花费较长时间。
此结构会经常增加,因此我正在寻找增加处理时间的原因。
答案 0 :(得分:8)
这仅仅是抖动有多聪明的问题。对于常规的局部int变量,该语句
x++;
在许多情况下,可以简化为单机指令,因为可以注册局部变量。如果未注册,则指令序列将是加载值,对其进行递增和存储,因此只有少量指令。
但是在结构上重载++
具有以下语义。假设我们在s
中有结构,我们说s++
。这意味着我们有效地实施了
s = S.operator++(s);
这是做什么的?
s
复制到本地变量位置,该位置是新的形式参数恢复上一个激活帧的状态
将返回的值复制到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进行该函数调用。甚至可以部分重新编译以进行此更改。真的很难预测是否以及何时这样做。