C++20から採用された [[likely]], [[unlikely]] 属性

2021年3月12日C++,Clang,LLVM,MSVC,Visual Studio

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

機能

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

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

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

使用例

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

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

MSVC

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対応しないんですか?と訊いたら、うちは対応しないですね。と返ってきたので、そういう方針なんでしょうね。下手な指定のされ方をされると、逆にパフォーマンスが下がる事もありますからね。

Clang

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

左が__buitin_expectあり、右が__buitin_expectなし

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

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

注意

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