下面有一个带有运行时for
循环
for(int i = 0; i < I; i++)
for (int j = 0; j < J; j++)
A( row(i,j), column(i,j) ) = f(i,j);
重复调用该代码段。循环边界'I'和'J'在编译时是已知的(I / J是2到10的顺序)。我想以某种方式使用模板展开循环。主要的瓶颈是row()和column()以及f()函数。我想用使用row<i,j>::enum
技巧在编译时评估的等效元程序替换它们。
我真正喜欢的是最终将循环解析为一系列陈述的内容,如:
A(12,37) = 0.5;
A(15,23) = 0.25;
A(14,45) = 0.25;
但是我想这样做而不会过多地破坏for
- for
结构。本着精神:
TEMPLATE_FOR<i,0,I>
TEMPLATE_FOR<j,0,J>
A( row<i,j>::value, column<i,j>::value ) = f<i,j>::value
可以使用boost :: lambda(或其他东西)来帮我创建吗?
答案 0 :(得分:12)
一个好的编译器应该为你展开。例如,在gcc中使用-O2选项进行编译会打开循环展开。
如果您尝试自己手动完成,除非您仔细测量并真正知道自己在做什么,否则最终会导致代码变慢。例如,在您手动展开的情况下,您可能会阻止编译器进行循环交换或stripmine优化(在the gcc docs中查找--floop-interchange和-floop-strip-mine)< / p>
答案 1 :(得分:7)
这是直接执行此操作的方法:
template <int i, int j>
struct inner
{
static void value()
{
A(row<i,j>::value, column<i,j>::value) = f<i,j>::value;
inner<i, j+1>::value();
}
};
template <int i> struct inner<i, J> { static void value() {} };
template <int i>
struct outer
{
static void value()
{
inner<i, 0>::value();
outer<i+1>::value();
}
};
template <> struct outer<I> { static void value() {} };
void test()
{
outer<0>::value();
}
如有必要,您可以将A
作为参数传递给每个value
。
这是一种使用可变参数模板的方法,不需要硬编码的I和J:
#include <utility>
template <int j, class Columns>
struct Inner;
template <class Columns, class Rows>
struct Outer;
template <int j, int... i>
struct Inner<j, std::index_sequence<i...>>
{
static void value() { (A(column<i, j>::value, row<i, j>::value), ...); }
};
template <int... j, class Columns>
struct Outer<std::index_sequence<j...>, Columns>
{
static void value() { (Inner<j, Columns>::value(), ...); }
};
template <int I, int J>
void expand()
{
Outer<std::make_index_sequence<I>, std::make_index_sequence<J>>::value();
}
void test()
{
expand<3, 5>();
}
(带有生成程序集的代码段:https://godbolt.org/g/DlgmEl)
答案 2 :(得分:5)
您可以使用Boost MPL。
循环展开的示例是mpl::for_each page。
for_each< range_c<int,0,10> >( value_printer() );
它似乎并没有在编译时进行全部评估,但它可能是一个很好的起点。
答案 3 :(得分:4)
查看Template Metaprograms和 bubble sort 实施。
答案 4 :(得分:4)
我会说这是一个错误的好主意。
在C ++中:
row<i,j>::value
意味着你将拥有与你拥有i * j一样多的不同row<>()
个函数。您不希望这样,因为它会增加代码的大小并执行大量指令缓存未命中。
当我在做模板函数以避免单个布尔检查时,我观察到了这一点。
如果是一个简短的函数只是内联它。
答案 5 :(得分:3)
你可以使用Boost.Mpl在编译时实现整个事情,但我不确定它会更快。 (Mpl基本上将所有STL算法重新实现为编译时元编程模板)
这种方法的问题在于,您最终会展开并内联很多代码,这可能会破坏指令缓存并消耗可能已保存的内存带宽。这可能会产生巨大,臃肿和缓慢的代码。
我可能更愿意相信编译器内联有意义的函数。只要row
和column
函数定义在循环中可见,编译器就可以简单地内联调用并展开它认为有益的迭代次数。
答案 6 :(得分:1)
f
需要返回double
- 这在编译时无法完成。
答案 7 :(得分:1)
如果您愿意稍微修改语法,可以执行以下操作:
template <int i, int ubound>
struct OuterFor {
void operator()() {
InnerFor<i, 0, J>()();
OuterFor<i + 1, ubound>()();
}
};
template <int ubound>
struct OuterFor <ubound, ubound> {
void operator()() {
}
};
在InnerFor中,我是外循环计数器(编译时常量),j是内循环计数器(最初为0 - 也是编译时常量),因此您可以将行计算为编译时模板。
它有点复杂,但正如你所说,无论如何,row(),col()和f()都是你复杂的部分。至少尝试一下,看看性能是否值得。调查其他选项以简化row()等功能可能是值得的。
答案 8 :(得分:1)
我从来没有尝试过这样做,所以要把这个想法带上一粒盐......
您似乎可以使用Boost.Preprocessor进行循环展开(特别是BOOST_PP_FOR和BOOST_PP_FOR_r宏),然后使用模板生成实际的常量表达式。
答案 9 :(得分:0)
我不是模板元编程的粉丝,所以你可能想要用一点点盐来回答这个问题。但是,在我将任何时间投入到这个问题之前,我会问自己以下几点:
for
循环是否成为瓶颈?在许多编译器/ cpu中,由于缓存效应,“循环”版本可以提供更好的性能。
请记住:先测量,然后再进行优化 - 如果有的话。