私は現在、すべてのサイクルが重要なプロジェクトに取り組んでいます。アプリケーションをプロファイリングしているときに、いくつかの内部ループのオーバーヘッドが非常に高いことがわかりました。これは、それらが少数の機械命令で構成されているためです。さらに、これらのループの反復回数はコンパイル時にわかります。
そのため、コピーと貼り付けでループを手動でアンロールする代わりに、マクロを使用してコンパイル時にループをアンロールし、後で簡単に変更できると考えました。
私がイメージするのはこのようなものです:
_#define LOOP_N_TIMES(N, CODE) <insert magic here>
_
for (int i = 0; i < N, ++i) { do_stuff(); }
を次のように置き換えることができるように:
_#define INNER_LOOP_COUNT 4
LOOP_N_TIMES(INNER_LOOP_COUNT, do_stuff();)
_
そして、以下に展開されます。
_do_stuff(); do_stuff(); do_stuff(); do_stuff();
_
ほとんどの場合、Cプリプロセッサはまだ私にとって謎なので、これを達成する方法はわかりませんが、Boostに_BOOST_PP_REPEAT
_マクロがあるように見えるので、それが可能である必要があることはわかっています。残念ながら、このプロジェクトではBoostを使用できません。
テンプレートを使用して展開できます。サンプルの分解を参照してくださいLive on Godbolt
しかし-funroll-loops
はこのサンプルでも同じ効果があります 。
template <unsigned N> struct faux_unroll {
template <typename F> static void call(F const& f) {
f();
faux_unroll<N-1>::call(f);
}
};
template <> struct faux_unroll<0u> {
template <typename F> static void call(F const&) {}
};
#include <iostream>
#include <cstdlib>
int main() {
srand(time(0));
double r = 0;
faux_unroll<10>::call([&] { r += 1.0/Rand(); });
std::cout << r;
}
プリプロセッサを使用して、トークン連結と複数のマクロ展開でいくつかのトリックをプレイできますが、すべての可能性をハードコーディングする必要があります。
#define M_REPEAT_1(X) X
#define M_REPEAT_2(X) X X
#define M_REPEAT_3(X) X X X
#define M_REPEAT_4(X) X X X X
#define M_REPEAT_5(X) X M_REPEAT_4(X)
#define M_REPEAT_6(X) M_REPEAT_3(X) M_REPEAT_3(X)
#define M_EXPAND(...) __VA_ARGS__
#define M_REPEAT__(N, X) M_EXPAND(M_REPEAT_ ## N)(X)
#define M_REPEAT_(N, X) M_REPEAT__(N, X)
#define M_REPEAT(N, X) M_REPEAT_(M_EXPAND(N), X)
そして、次のように展開します。
#define THREE 3
M_REPEAT(THREE, three();)
M_REPEAT(4, four();)
M_REPEAT(5, five();)
M_REPEAT(6, six();)
この方法では、カウントとしてリテラル数が必要です。次のようなことはできません。
#define COUNT (N + 1)
M_REPEAT(COUNT, stuff();)
これを行う標準的な方法はありません。
これはやや厄介なアプローチです:
#define DO_THING printf("Shake it, Baby\n")
#define DO_THING_2 DO_THING; DO_THING
#define DO_THING_4 DO_THING_2; DO_THING_2
#define DO_THING_8 DO_THING_4; DO_THING_4
#define DO_THING_16 DO_THING_8; DO_THING_8
//And so on. Max loop size increases exponentially. But so does code size if you use them.
void do_thing_25_times(void){
//Binary for 25 is 11001
DO_THING_16;//ONE
DO_THING_8;//ONE
//ZERO
//ZERO
DO_THING;//ONE
}
デッドコードを排除するためにオプティマイザに依頼することはそれほど多くありません。その場合:
#define DO_THING_N(N) if(((N)&1)!=0){DO_THING;}\
if(((N)&2)!=0){DO_THING_2;}\
if(((N)&4)!=0){DO_THING_4;}\
if(((N)&8)!=0){DO_THING_8;}\
if(((N)&16)!=0){DO_THING_16;}
「unroll-count」の計算に#defineコンストラクトを使用することはできません。しかし、十分なマクロがあれば、これを定義できます:
#define LOOP1(a) a
#define LOOP2(a) a LOOP1(a)
#define LOOP3(a) a LOOP2(a)
#define LOOPN(n,a) LOOP##n(a)
int main(void)
{
LOOPN(3,printf("hello,world"););
}
VC2012でテスト済み
real再帰ステートメントをマクロで書くことはできず、realマクロでの繰り返し。
ただし、 Order を確認できます。完全にCプリプロセッサの上に構築されていますが、反復のような機能を「実装」しています。実際には、最大N回の反復が可能です。ここで、Nは大きな数です。 「再帰」マクロの場合も同様だと思います。とにかく、それは境界コンパイラのケースであり、ほとんどのコンパイラがサポートしていません(ただし、GCCはその1つです)。