在C ++中模板化'for'循环?

时间:2009-06-23 13:31:30

标签: c++ optimization templates metaprogramming

下面有一个带有运行时for循环

的C ++代码段
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(或其他东西)来帮我创建吗?

10 个答案:

答案 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算法重新实现为编译时元编程模板)

这种方法的问题在于,您最终会展开并内联很多代码,这可能会破坏指令缓存并消耗可能已保存的内存带宽。这可能会产生巨大,臃肿和缓慢的代码。

我可能更愿意相信编译器内联有意义的函数。只要rowcolumn函数定义在循环中可见,编译器就可以简单地内联调用并展开它认为有益的迭代次数。

答案 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_FORBOOST_PP_FOR_r宏),然后使用模板生成实际的常量表达式。

答案 9 :(得分:0)

我不是模板元编程的粉丝,所以你可能想要用一点点盐来回答这个问题。但是,在我将任何时间投入到这个问题之前,我会问自己以下几点:

  1. 我的for循环是否成为瓶颈?
  2. 展开会有什么不同吗?通过手动展开和测量轻松测试。
  3. 在许多编译器/ cpu中,由于缓存效应,“循环”版本可以提供更好的性能。

    请记住:先测量,然后再进行优化 - 如果有的话。