当我在正常的一天进行编程时,我会确保所有分支都很可能没有被占用。
int retval = do_somting();
if(!retval) { /* Less-than-likely event*/ }
这会优化分支预测,导致CPU的预测位设置为"不接受"。然而,预测器位在for循环后会被迫返回"take"吗?
// prediction = "likely take"
if(false) { }
// prediction = "probably take"
if(false) { }
// prediction = "probably not take"
if(false) { }
// prediction = "likely not take"
if(false) { }
/* ... thousands of other if(false) that are speedy-fast */
for(int i = 0; i < 5; i++) { }
// prediction = "likely take"?
我知道这是一个不切实际的微小优化,但嘿,你知道的越多。
EDIT:让我们假设GCC没有丢弃上面的所有代码,并且我们也只讨论amd64体系结构。因为我没有意识到这个问题有多低级
事实证明,分支预测取决于CPU的模型。
根据本文,当将循环与正常分支关联时,分支预测可以用无数种方式进行处理。有些CPU有一个单独的预测器循环。这意味着if
语句根本不会影响对for
语句的预测。其他人也有同样的预测。
无论如何,这个问题没有一个真正的答案。因为在谈论分支效率时,不需要测量环路。
当然,除非你计划只在一个单一型号的CPU上运行你的程序。
大多数具有分支预测的体系结构(包括AMD64)都认为不太可能出现短的向下/向前跳跃/分支,而可能出现短暂的向上/向后分支/跳跃。这意味着大多数循环被预测为继续循环。由于初始条件,这使得do-while循环比for循环或while循环的效率略高;然而,大多数优化编译器会在可能的情况下将这些情况优化为类似的代码。
通过使用带有__builtin_expect()
的条件,您可以看到在-O3优化级别上使用gcc进行组装的差异。不太可能的分支通常是向前跳跃,而可能的条件要么根本不分支,要么向后跳跃。这可能涉及反转逻辑。注意:在-O3中,gcc通常会将代码复制到不太可能的分支中,以便在可能的情况下将分支最小化。
这是有道理的,因为适合缓存行的循环如果分支到其开头,就不会有缓存未命中。类似地,由于程序通常在函数内线性前进,因此最近执行的代码也可能已经在缓存中。当您用一堆额外的"优化"条件替换循环时,在某个时刻(可能在4个条件左右),缓存未命中将覆盖您可能以可读性和可维护性为代价获得的任何微小好处。