C/C++ 中的自展开宏循环
Self-unrolling macro loop in C/C++
我目前正在做一个项目,每个周期都很重要。在分析我的应用程序时,我发现某些内部循环的开销非常高,因为它们只包含一些机器指令。此外,这些循环中的迭代次数在编译时是已知的。
所以我想与其通过复制和粘贴手动展开循环,不如在编译时使用宏来展开循环,以便以后可以轻松修改它。
我的图片是这样的:
#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。
没有执行此操作的标准方法。
这是一个有点疯狂的方法:
#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;}
你不能用宏写 real 递归语句,我很确定你不能在宏中有 real 迭代还有。
不过你可以看看Order。虽然它完全构建在 C 预处理器之上,但它具有 "implements" 类似迭代的功能。它实际上最多可以有 N 次迭代,其中 N 是一个很大的数字。我猜它与 "recursive" 宏类似。无论如何,这是一种边缘情况,很少有编译器支持它(不过 GCC 就是其中之一)。
您可以使用预处理器并玩一些令牌连接和多个宏扩展的技巧,但您必须硬编码所有可能性:
#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 结构来计算"unroll-count"。但是如果有足够的宏,你可以这样定义:
#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 测试
您可以使用模板展开。
查看示例的反汇编 Live on Godbolt
但是-funroll-loops
has the same effect for this sample.
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 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。
没有执行此操作的标准方法。
这是一个有点疯狂的方法:
#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;}
你不能用宏写 real 递归语句,我很确定你不能在宏中有 real 迭代还有。
不过你可以看看Order。虽然它完全构建在 C 预处理器之上,但它具有 "implements" 类似迭代的功能。它实际上最多可以有 N 次迭代,其中 N 是一个很大的数字。我猜它与 "recursive" 宏类似。无论如何,这是一种边缘情况,很少有编译器支持它(不过 GCC 就是其中之一)。
您可以使用预处理器并玩一些令牌连接和多个宏扩展的技巧,但您必须硬编码所有可能性:
#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 结构来计算"unroll-count"。但是如果有足够的宏,你可以这样定义:
#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 测试
您可以使用模板展开。 查看示例的反汇编 Live on Godbolt
但是-funroll-loops
has the same effect for this sample.
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;
}