Vik*_*ehr 3 c++ algorithm micro-optimization compiler-optimization likely-unlikely
在 Visual Studio 中阅读标准库算法的实现时,他们根本不使用[[likely]]/属性。[[unlikely]]对我来说,应该使用的教科书示例[[unlikely]]是例如std::find_if(...),微软已经这样实现:
// Note that some noise are removed from the code
template <class _InIt, class _Pr>
_InIt find_if(_InIt _First, const _InIt _Last, _Pr _Pred) {
for (; _First != _Last; ++_First) {
if (_Pred(*_First)) {
break;
}
}
return _First;
}
Run Code Online (Sandbox Code Playgroud)
如前所述,find_if(...)这是 if 子句的 true 分支不太可能的教科书示例,因为大多数情况下它会在谓词验证为 true 之前迭代几个元素,因此 [[unlikely]] 将是一个优化机会。Microsoft 没有[[unlikely]]在这里使用该属性有什么原因吗?
Pet*_*des 10
[[likely]][[unlikely]]仅在针对特定用例进行调整时才是一个好主意。您必须知道,例如,第一个元素匹配的情况并不常见,并且迭代器范围大多数时候超过 0 或 1 个元素。相对简单函数的标准库头文件与使用这些属性的好地方相反。(也许排序函数深处的某个地方可能是有意义的。)
跳出循环对于编译器来说已经相当“明显”了:它们猜测分支是否可能或不可能的启发式已经知道if()break每次运行整个循环时分支最多为真一次。
此外,这只是告知如何布局代码,而不是直接提示 CPU 硬件预测器(甚至仅在少数罕见的 ISA 上可能,除了回退到静态预测,这在现代 x86 上甚至不再是一个事情)。请参阅是否可以告诉分支预测器遵循分支的可能性有多大?(在除 Pentium 4 之外的 x86 上没有,在 ARM 或 AArch64 上也没有,因此任何 ISA MSVC 目标)。
而且打破循环的选择并不多。使用 if/else,您可以使一条路径成为一条直线(零采用分支,以及理想的 I-cache 局部性),而另一条路径必须跳转到另一个块并返回(例如,您将其放置在函数末尾)。 ) 请参阅 我可以使用我的代码改进分支预测吗?获取最新的概述/要点likely/unlikely内容摘要以及更多链接。
我想如果您知道一种离开循环的方法比另一种更有可能,您可能更喜欢循环底部的该分支,作为 asmdo{}while() 循环结构的底部,因此另一个分支可以(几乎)永远不会采取,并且取决于分支预测硬件,也许对其他分支的污染更少?
如果 CPU 中没有使用静态预测作为后备的分支条目,并且前向不采取的预测是正确的,则 CPU 可能不会逐出任何其他预测条目来为该预测条目腾出空间。仅当/当它第一次预测错误时才这样做。因此,如果编译器知道循环最有可能采用多个可能的退出中的哪一个,并且可以廉价地旋转它以使其中一个位于底部,那么这可能是一个小小的胜利。但我们不知道 STL 函数中的这一点。
(同样,在具有 IT-TAGE 预测器的现代 x86 上,这不是一个因素。条件分支没有静态预测,因为动态预测器将为每个分支生成猜测。在解码之前,它可能没有目标地址,并且如果最近没有看到分支执行,则根本不是间接分支,而是if()break直接条件分支。 *为什么英特尔这些年改变静态分支预测机制?)
是否[[unlikely]]会if()break暗示编译器更有可能通过其他终止条件退出循环,即到达范围末尾而不找到任何匹配项? 我们绝对不想这样做,在某些用例中可能不是这样。最好让编译器应用其正常的启发式方法来猜测在将此循环内联到调用者时要支持哪些代码路径。
TL:DR:基本上没有什么收获。
您是否尝试过并在优化的构建中找到更好的汇编?(使用 MSVC,因为这是来自 MSVC 的库)。如果这不会让其他用例变得更糟,那么请考虑提交一份错过优化的错误报告(在微软的论坛上?我不确定他们如何接受这样的报告)。最有可能的是,编译器开发人员宁愿调整编译器的启发式方法来改进所有类似循环的代码生成,而不是用[[likely]]或使标准库变得混乱[[unlikely]]。
| 归档时间: |
|
| 查看次数: |
182 次 |
| 最近记录: |