为已知的更常见路径优化分支

请考虑以下代码:

void error_handling(); bool method_impl(); bool method() { const bool res = method_impl(); if (res == false) { error_handling(); return false; } return true; } 

我知道method_impl()将返回true 99.999%(是的,三位小数)的时间,但我的编译器没有。 method()在时间消耗方面是部分关键的。

  1. 我应该重写method() (并使其不太可读)以确保只有当method_impl()返回false时才会发生跳转? 如果有,怎么样?
  2. 我应该让编译器为我做这项工作吗?
  3. 我应该让我的CPU的分支预测为我做的工作吗?

底层硬件已经执行了这种优化。 它将“失败”第一次预测它,但在它将达到正确选项en.wikipedia.org/wiki/Branch_predictor之后。

您可以尝试应用GCC扩展并检查它是否更快,但我认为您几乎看不到它与它没有任何区别。 始终应用分支预测,而不是您启用的分支预测

您可以建议编译器method_impl()将返回true:

 void error_handling(); bool method_impl(); bool method() { const bool res = method_impl(); if (__builtin_expect (res, 0) == false) { error_handling(); return false; } return true; } 

这将在GCC中有效。

根据其他答案的建议,我对解决方案进行了基准测试。 如果你考虑提出这个答案,那么也请投票给其他人。

基准代码

 #include  #include  #include  // solutions #include  // benchmak #include  #include  #include  #include  #include  // // Solutions // namespace { volatile std::time_t near_futur = -1; void error_handling() { std::cerr << "error\n"; } bool method_impl() { return std::time(NULL) != near_futur; } bool method_no_builtin() { const bool res = method_impl(); if (res == false) { error_handling(); return false; } return true; } bool method_builtin() { const bool res = method_impl(); if (__builtin_expect(res, 1) == false) { error_handling(); return false; } return true; } bool method_rewritten() { const bool res = method_impl(); if (res == true) { return true; } else { error_handling(); return false; } } } // // benchmark // constexpr std::size_t BENCHSIZE = 50'000'000; class Clock { std::chrono::time_point _start; public: static inline std::chrono::time_point now() { return std::chrono::steady_clock::now(); } Clock() : _start(now()) { } template std::size_t end() { return std::chrono::duration_cast(now() - _start).count(); } }; // // Entry point // int main() { { Clock clock; bool result = true; for (std::size_t i = 0 ; i < BENCHSIZE ; ++i) { result &= method_no_builtin(); result &= method_no_builtin(); result &= method_no_builtin(); result &= method_no_builtin(); result &= method_no_builtin(); result &= method_no_builtin(); result &= method_no_builtin(); result &= method_no_builtin(); result &= method_no_builtin(); result &= method_no_builtin(); } const double unit_time = clock.end() / static_cast(BENCHSIZE); std::cout << std::setw(40) << "method_no_builtin(): " << std::setprecision(3) << unit_time << " ns\n"; } { Clock clock; bool result = true; for (std::size_t i = 0 ; i < BENCHSIZE ; ++i) { result &= method_builtin(); result &= method_builtin(); result &= method_builtin(); result &= method_builtin(); result &= method_builtin(); result &= method_builtin(); result &= method_builtin(); result &= method_builtin(); result &= method_builtin(); result &= method_builtin(); } const double unit_time = clock.end() / static_cast(BENCHSIZE); std::cout << std::setw(40) << "method_builtin(): " << std::setprecision(3) << unit_time << " ns\n"; } { Clock clock; bool result = true; for (std::size_t i = 0 ; i < BENCHSIZE ; ++i) { result &= method_rewritten(); result &= method_rewritten(); result &= method_rewritten(); result &= method_rewritten(); result &= method_rewritten(); result &= method_rewritten(); result &= method_rewritten(); result &= method_rewritten(); result &= method_rewritten(); result &= method_rewritten(); } const double unit_time = clock.end() / static_cast(BENCHSIZE); std::cout << std::setw(40) << "method_rewritten(): " << std::setprecision(3) << unit_time << " ns\n"; } } 

基准测试结果

g++ -std=c++14 -O2 -Wall -Wextra -Werror main.cpp

  method_no_builtin(): 45 ns method_builtin(): 44.8 ns method_rewritten(): 44.5 ns 

演示

g++ -std=c++14 -O3 -Wall -Wextra -Werror main.cpp

  method_no_builtin(): 32.9 ns method_builtin(): 32.1 ns method_rewritten(): 32.3 ns 

演示

结论

这些优化之间的差异太小而无法得出任何结论:如果在为已知的更常见路径优化分支时找到性能增益,则此增益太小而不值得麻烦和可读性损失。

在不知道std :: time()的实现的情况下,我不会从这个测试中得出很多结论。 从你自己的结果来看,它似乎占据了循环中的时间。

FWIW,我在调优代码时自己使用了很可能()/不可能()。 我不想改变代码的结构,但是在阅读程序集时,我希望看到共同的路径是一条直线的未分支的分支。 这里的关键(对我而言)是组件的可读性。 这也是最快的事实是次要的(最快的分支是正确预测的未分支分支)。