web-dev-qa-db-ja.com

整数の範囲を指定してオプティマイザーにヒントを与えることはできますか?

int型を使用して値を保存しています。プログラムのセマンティクスにより、値は常に非常に小さい範囲(0〜36)で変化し、intcharではなく)はCPUの効率のためにのみ使用されます。

このような狭い範囲の整数に対して、多くの特別な算術最適化を実行できるようです。これらの整数に対する多くの関数呼び出しは、小さな「魔法の」操作のセットに最適化される場合があり、一部の関数はテーブル検索に最適化される場合もあります。

それで、このintが常にその狭い範囲にあることをコンパイラーに伝えることは可能ですか?コンパイラーはそれらの最適化を行うことは可能ですか?

171
rolevax

はい、可能です。たとえば、gccの場合、__builtin_unreachableを使用して、次のように不可能な条件をコンパイラーに通知できます。

if (value < 0 || value > 36) __builtin_unreachable();

上記の条件をマクロでラップできます。

#define assume(cond) do { if (!(cond)) __builtin_unreachable(); } while (0)

そして次のように使用します:

assume(x >= 0 && x <= 10);

ご覧のとおりgccは、この情報に基づいて最適化を実行します。

#define assume(cond) do { if (!(cond)) __builtin_unreachable(); } while (0)

int func(int x){
    assume(x >=0 && x <= 10);

    if (x > 11){
        return 2;
    }
    else{
        return 17;
    }
}

生産物:

func(int):
    mov     eax, 17
    ret

ただし、1つの欠点は、コードがこのような仮定を破った場合、未定義の動作が発生することです。

デバッグビルドであっても、これが発生しても通知されません。仮定のあるバグをより簡単にデバッグ/テスト/キャッチするには、次のようなハイブリッドの仮定/アサートマクロ(@David Zのクレジット)を使用できます。

#if defined(NDEBUG)
#define assume(cond) do { if (!(cond)) __builtin_unreachable(); } while (0)
#else
#include <cassert>
#define assume(cond) assert(cond)
#endif

デバッグビルド(NDEBUGnot定義)では、通常のassertのように機能し、エラーメッセージとabort 'ingプログラムを出力し、リリースビルドでは仮定の使用、最適化されたコードの生成。

ただし、通常のassertの代わりではないことに注意してください。condはリリースビルドに残っているため、assume(VeryExpensiveComputation())のようなことはしないでください。

227
deniss

これには標準サポートがあります。あなたがすべきことは、stdint.hcstdint)を含めてから、タイプuint_fast8_tを使用することです。

これは、0〜255の数字のみを使用していることをコンパイラに伝えますが、より高速なコードを提供する場合は、より大きな型を自由に使用できます。同様に、コンパイラーは変数の値が255を超えることはないと想定し、それに応じて最適化を実行できます。

60
Lundin

現在の答えは、範囲が何であるかを知っている場合に適していますが、値が範囲外の場合でも正しい動作が必要な場合予想される範囲、それは動作しません。

その場合、この手法が機能することがわかりました。

if (x == c)  // assume c is a constant
{
    foo(x);
}
else
{
    foo(x);
}

アイデアはコードとデータのトレードオフです:datax == c)の1ビットを制御ロジック
これは、オプティマイザーにxが実際に既知の定数cであることを示唆し、fooの最初の呼び出しを残りとは別に、おそらくかなりインライン化および最適化するように促します。重く。

ただし、実際にはコードを単一のサブルーチンfooに分解してください。コードを複製しないでください。

例:

この手法を機能させるには、少し幸運である必要があります。コンパイラが物事を静的に評価しないことを決定する場合があり、それらは一種のarbitrary意的なものです。しかし、それが機能するとき、それはうまく機能します:

#include <math.h>
#include <stdio.h>

unsigned foo(unsigned x)
{
    return x * (x + 1);
}

unsigned bar(unsigned x) { return foo(x + 1) + foo(2 * x); }

int main()
{
    unsigned x;
    scanf("%u", &x);
    unsigned r;
    if (x == 1)
    {
        r = bar(bar(x));
    }
    else if (x == 0)
    {
        r = bar(bar(x));
    }
    else
    {
        r = bar(x + 1);
    }
    printf("%#x\n", r);
}

-O3を使用し、事前評価済みの定数0x20および0x30eアセンブラー出力 で使用するだけです。

9
Mehrdad

より標準的なC++のソリューションが必要な場合は、[[noreturn]]属性を使用して独自のunreachableを記述することができます。

だから私は再利用します deniss '素晴らしい例

namespace detail {
    [[noreturn]] void unreachable(){}
}

#define assume(cond) do { if (!(cond)) detail::unreachable(); } while (0)

int func(int x){
    assume(x >=0 && x <= 10);

    if (x > 11){
        return 2;
    }
    else{
        return 17;
    }
}

ご覧のとおり の場合、ほとんど同じコードになります。

detail::unreachable():
        rep ret
func(int):
        movl    $17, %eax
        ret

マイナス面はもちろん、[[noreturn]]関数が実際に戻るという警告を受け取ることです。

5
StoryTeller