C 哪个更有效?重复作业或重复检查

C 哪个更有效?重复作业或重复检查,c,performance,optimization,processing-efficiency,C,Performance,Optimization,Processing Efficiency,如果我有一个循环来检查数组中的特定值,并且由于某种原因,必须迭代所有元素,并且不能中途中断 以下哪一项效率更高:在每个匹配项上盲目设置标志,或者在设置标志之前检查标志是否为false bool happened = false; while (...) { if (...) { happened = true; } } vs 据我所知,这两者或多或少是等效的,前提是内存读取速度与内存写入速度一样快(忽略第二个示例中的额外指令)。我的结论正确吗?只有分析才能确定,但最有可能的是变

如果我有一个循环来检查数组中的特定值,并且由于某种原因,必须迭代所有元素,并且不能中途中断

以下哪一项效率更高:在每个匹配项上盲目设置标志,或者在设置标志之前检查标志是否为false

bool happened = false;
while (...) {
  if (...) {
    happened = true;
  }
}
vs


据我所知,这两者或多或少是等效的,前提是内存读取速度与内存写入速度一样快(忽略第二个示例中的额外指令)。我的结论正确吗?

只有分析才能确定,但最有可能的是变量存储在寄存器中,而不是内存中(除非循环中有许多其他局部变量),并且根本没有可测量的差异。

是的,您非常正确。他们或多或少是一样的

确切地说,如果“发生”次数较多,则第一种形式可能更可取,而如果“发生”次数较少,则第二种形式可能更可取。但即使这样,我也不认为第二个更好


最后,它可能是一种微优化,最好是追求可读性而不是性能。

一般来说,第一个版本更适合流水线,因为它的指令流受跳转的干扰更少,因此效率更高。但这取决于特定的体系结构特性和编译器优化


我相信这两个版本的性能差异在实际情况下是不明显的。

我已经在以下代码上运行了它:

bool happened = false;
for (int i = 0; i < 1000000000; i++) {
  if (i % 2) {
    // uncomment the one you want to use
    // happened = true;
    // if (!happened) happened = true;
  }
}
bool-occulated=false;
对于(int i=0;i<100000000;i++){
如果(i%2){
//取消对要使用的文件的注释
//发生=真实;
//如果(!已发生)已发生=真;
}
}

我在每台电脑上运行了10次,第一台电脑的平均值为2.304s,标准偏差为0.013s,第二台电脑的平均值为2.399s,标准偏差为0.007s。两个样本的t检验表明
occurrent=true
的速度快,如果(!已发生)已发生=true
bool happened = false;
extern volatile int dontOptMe1, dontOptMe2, dontOptMe3;
while (dontOptMe1) {
  if (dontOptMe2) {
    happened = true;
  }
}
dontOptMe3 = happened;
vs

导致出现以下伪ASM:

  MOV      happened, 0
  BRA      LOOP_END
LOOP_START:
  SELECTEQ dontOptMe2, 0, happened, happened, 1
LOOP_END:
  BCZC     dontOptMe1, LOOP_START
EXIT:
  STORE    dontOptMe3, happened
vs

第一种更可取。这也是限制volatile类型的一个很好的例子。我很惊讶编译器不能将第二个转换成第一个


注意:选择XX表示,如果Arg1减去Arg2设置了条件代码XX,则将Arg3设置为Arg4,否则将Arg3设置为Arg5。所以:
SELECTNE dontOptMe2,0,R2,1,0
是否等同于:
R2=(dontOptMe2==0)?1 : 0;
如果您有条件地将某个值设置为某个值(如果它不是某个值),那么它在逻辑上与无条件地设置该值相同。如果幸运的话,编译器会注意到并以相同的方式处理这两个版本,但如果没有注意到,则无条件版本每次都会赢
1:它使用的指令更少
2:额外指令是有条件的,会影响分支预测

如果你和

 if (cond) varx = vary;
编译器使用一个
条件分支(如果硬件支持,可能是条件移动而不是分支)

如果你和我一起去的话

 if (cond && varx != vary) varx = vary;

编译器要么简化为第一种情况,要么使用两个条件跳转(或者一个跳转和一个条件移动)。

按照你的问题的措辞,很难准确地说出你在问什么,但我认为这取决于你的访问模式。在数组上迭代可能会很昂贵,但分支预测失误也是如此。我怀疑这会有什么不同,只是检查看起来有点愚蠢。虽然没有分析就不能说“效率”和任何真正的性能差异,但第一个在理论上似乎更好。因为当您决定将
occurrent
设置为true时,您不必担心它的当前值,额外的检查(在第2行)似乎有点过火。该示例似乎简化了很多。如果您正在分析优化的代码,任何一个好的编译器都会优化出发生的变量(很可能它会优化出整个循环)。分析未优化的代码没有意义。我使用
-O0
编译,并检查程序集以确保所有内容都在那里,因此我可以确认我正在运行未优化的代码。我不同意你的观点,即评测未优化的代码毫无意义——这两个二进制文件是相同的,除了在最里面的循环中发生的事情。所以唯一改变的是我们正在比较的东西。
  MOV      happened, 0
  BCZS     dontOptMe1, EXIT
LOOP:
  SELECTNE dontOptMe2, 0, R2, 1, 0
  SELECTEQ happened, 0, R3, 1, 0
  AND      R3, R2, R3
  SELECTNE R3, 0, happened, 1, 0
  BCZC     dontOptMe1, LOOP
EXIT:
  STORE    dontOptMe3, happened
 if (cond) varx = vary;
 if (cond && varx != vary) varx = vary;