int
型を使用して値を保存しています。プログラムのセマンティクスにより、値は常に非常に小さい範囲(0〜36)で変化し、int
(char
ではなく)はCPUの効率のためにのみ使用されます。
このような狭い範囲の整数に対して、多くの特別な算術最適化を実行できるようです。これらの整数に対する多くの関数呼び出しは、小さな「魔法の」操作のセットに最適化される場合があり、一部の関数はテーブル検索に最適化される場合もあります。
それで、このint
が常にその狭い範囲にあることをコンパイラーに伝えることは可能ですか?コンパイラーはそれらの最適化を行うことは可能ですか?
はい、可能です。たとえば、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
デバッグビルド(NDEBUG
not定義)では、通常のassert
のように機能し、エラーメッセージとabort
'ingプログラムを出力し、リリースビルドでは仮定の使用、最適化されたコードの生成。
ただし、通常のassert
の代わりではないことに注意してください。cond
はリリースビルドに残っているため、assume(VeryExpensiveComputation())
のようなことはしないでください。
これには標準サポートがあります。あなたがすべきことは、stdint.h
(cstdint
)を含めてから、タイプuint_fast8_t
を使用することです。
これは、0〜255の数字のみを使用していることをコンパイラに伝えますが、より高速なコードを提供する場合は、より大きな型を自由に使用できます。同様に、コンパイラーは変数の値が255を超えることはないと想定し、それに応じて最適化を実行できます。
現在の答えは、範囲が何であるかを知っている場合に適していますが、値が範囲外の場合でも正しい動作が必要な場合予想される範囲、それは動作しません。
その場合、この手法が機能することがわかりました。
if (x == c) // assume c is a constant
{
foo(x);
}
else
{
foo(x);
}
アイデアはコードとデータのトレードオフです:data(x == 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
を アセンブラー出力 で使用するだけです。
より標準的な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]]
関数が実際に戻るという警告を受け取ることです。