[[likely]]が役立った話

この記事はstackoverflowで質問した内容の結論をまとめたものです.

正直, [[likely]] attribute なんて今まで一度も使ったことなかったです.

こんなコードを書いた

#include <stdio.h>
#define N 19

int main(void){
    int a[N];
    int ans = 0;

    for(int i = 0; i < N; ++i){
        a[i] = 0;
    }
    for(;;){
        int i;
        ++ans;
        for(i = N - 1; a[i] == 2; --i){
            if(i == 0){
                printf("%d\n", ans);
                return 0;
            }else{
                a[i] = 0;
            }
        }
        ++a[i];
    }
}

これは, 0 以上 3 未満の整数から重複を許して 19 個選ぶやり方  3^{19} 通りを,ループで数え上げて出力するコードです.

これを, gccコンパイルして実行時間をはかります.このとき, -O1 オプションを付けた場合と, -O2 オプションを付けた場合の 2 通りためしてみます.

すると,結果は次のようになりました(単位はすべて秒):

オプション 1回目 2回目 3回目 4回目 5回目 平均
-O1 0.70 0.71 0.69 0.71 0.68 0.70
-O2 1.13 1.11 1.10 1.11 1.09 1.11

-O2 の方が遅い!!びっくりして stackoverflow で質問を投げたら, -O2 で有効になる最適化のうち,-falign-functions -falign-jumps -falign-labels -falign-loopsがどうやら悪い方に働いていると言われました.さらに [[likely]] attribute を付けることも提案されました.

[[likely]] [[unlikely]] って?

[[likely]] は,実行される確率の高い文に付けます.[[unlikely]] は,実行される確率の低い文に付けます.

付けてみた

今回,for(;;)文で無限ループを書いていて,  3^{19} 回はループが回ります. break されるのは最後の一回だけなので,この for 文は「ループが終了する確率より,続く確率の方がはるかに高い」ということになります.

そこで,次のように[[likely]]を付けてみます.

#include <stdio.h>
#define N 19

int main(void){
    int a[N];
    int ans = 0;

    for(int i = 0; i < N; ++i){
        a[i] = 0;
    }
    for(;;) [[likely]] {
        int i;
        ++ans;
        for(i = N - 1; a[i] == 2; --i){
            if(i == 0){
                printf("%d\n", ans);
                return 0;
            }else{
                a[i] = 0;
            }
        }
        ++a[i];
    }
}

すると……

オプション 1回目 2回目 3回目 4回目 5回目 平均
-O1 0.65 0.65 0.66 0.66 0.66 0.66
-O2 0.66 0.65 0.66 0.65 0.66 0.66

おお! -O2 も速くなった!!

結論

[[likely]]でこんな影響が出ることも,あるんだなぁ