C++20から採用された [[likely]], [[unlikely]] 属性は条件分岐の最適化のヒントを与える機能

C++,Clang,LLVM,MSVC,Visual Studio

C++20から、likely, unlikely という attribute が利用可能になったので軽く触ってみました。

確率が高い分岐と低い分岐を伝える属性 [[likely]], [[unlikely]] – cpprefjp C++日本語リファレンス


機能

条件分岐の際に、どちらに分岐する可能性が高いかを示すことで、コンパイラに最適化のヒントを与える機能です。偏りの有る条件分岐に使うのが一般的です。

具体的には、if分の分岐で9割はtrueという事が分かっていればtrueになる方に[[likely]]を指定します。そうする事でコンパイラが条件分岐の偏りを意識した最適化を行います。

likelyはGCC, Clangで採用されていた __builtin_expect をC++標準機能にした物です。

注意

[[likely]], [[unlikely]], __builtin_expect の指定を逆にすると非効率的なコードが出力されます。

使用例

よくある使い方は、独自 assert の実装です。

#define my_assert(expr)                                                        \
  if (!(expr)) [[unlikely]] {                                                  \
    ::__debugbreak();                                                          \
  }

MSVC

MSVCの各C++規格への対応状況はこちらのサイトで確認できます。
Microsoft C/C++ 言語の準拠 | Microsoft Docs

Visual Studio 2019(バージョン16.7.6)でも使えるようになっているので、試してみます。
[[likely]], [[unlikely]] を利用するには、 /std:c++latest オプションが必要です。

assertだと処理が途中で止まってしまうので、printfをする簡単な分岐コードを書いています。

#include <cstdint>
#include <cstdio>

int main() {
  for (uint64_t i = 0; i < 100; ++i) {
    if (i % 10 != 0) [[likely]] {
      printf("true");
    } else {
      printf("false");
    }
  }
}
左が[[likely]]あり、右が[[likey]]なし

逆アセンブリを確認しましたが、全然差分がないので、コンパイル出来るだけで効果はないですね。
むしろ、最初から分岐処理が最適化されているように見えますね・・・

以前に、Microsoftの人と話をした時に、__builtin_expect対応しないんですか?と訊いたら、うちは対応しないですね。と返ってきたのでその方針から変わっていないのだと思います。

likely, unlikelyは下手な指定のされ方をされると逆にパフォーマンスが下がる事もあるので、そういう判断があってもおかしくはないと思います。

Clang

Visual Studioでも Clang 10.0.0 が直ぐに使えるので、Clangでも試しました。
Clang 10.0.0 だと [[lilely]] は未対応でしたので、__builtin_expect を使いました。

LLVM Branch Weight Metadata — LLVM 13 documentation

左が__buitin_expectあり、右が__buitin_expectなし

true, falseの分岐は、rdi と r14 レジスタの値を見て切り替えています。
__builtin_expectなしの方は、ループ処理が上の方にあり、下の方で rdi r14 の為に、cmp al,bl の分岐で jump 先を切り替えています。
__builtin_expectありの方は、先に先に分岐の計算をしてしまい、基本的に true になるだろうという事で、その先にループ処理がありますね。

結果的に、jmp命令が消えて、少しだけコードがすっきりしていますね。