【问题标题】:C++ template for unrolling a loop using a switch?使用开关展开循环的 C++ 模板?
【发布时间】:2013-06-07 10:48:31
【问题描述】:

我的问题类似于Can one unroll a loop when working with an integer template parameter?,但我想混合编译时间和运行时。具体来说,我在编译时知道一个常量NBLOCK,我想在变量start_block 上编写一个开关,该变量仅在运行时知道,其中NBLOCK 是开关中的条目数。这是我使用宏得到的结果:

#define CASE_UNROLL(i_loop)         \
  case i_loop : \
    dst.blocks[i_loop+1] -= (load_unaligned_epi8(srcblock) != zero) & block1; \
    srcblock += sizeof(*srcblock);

  switch(start_block)
    {
      CASE_UNROLL(0);
#if NBLOCKS > 2
      CASE_UNROLL(1);
#endif
#if NBLOCKS > 3
      CASE_UNROLL(2);
#endif
#if NBLOCKS > 4
      CASE_UNROLL(3);
#endif
...
...
#if NBLOCKS > 15
      CASE_UNROLL(14);
#endif
#if NBLOCKS > 16
#error "Too many blocks"
#endif
    }

我觉得它很丑。特别是如果我想将界限从 16 提高到 32。

我想知道是否可以使用一些模板元编程来编写它。困难的部分是,出于性能原因,使用跳转表而不是嵌套条件序列来编译开关至关重要。

请注意,这个问题与C++/C++11 - Switch statement for variadic templates? 非常相似,但据我了解,这里提出的解决方案是通过使用混合编译/调谐时间初始化函数指针数组来删除开关。我不能付钱给王子在这里调用一个函数。

如果需要一些讨厌的扩展,我正在使用 GCC。

【问题讨论】:

  • 不,它不是重复的。就我而言,起点在运行时已知,终点在编译时已知。 GCC 能够展开代码,但它使用了很多跳转。这个版本要快得多,因为只有一次跳转。但是它使用宏。我宁愿写一个模板。
  • 我问为什么?真的比编译后的代码快吗?
  • 是的,速度提高了 30%。这里的开关在我的内部计算循环中。

标签: c++ templates c++11 switch-statement template-meta-programming


【解决方案1】:

您可以简单地将 Boost.Preprocessor 与 BOOST_PP_REPEAT(COUNT, MACRO, DATA) 一起使用:

#define APPLY_FUNC(INDEX, FUNC) FUNC(INDEX);

// ...

switch(start_block)
{
    BOOST_PP_REPEAT(NBLOCK, APPLY_FUNC, CASE_UNROLL);
}

应该扩展为:

switch(start_block)
{
    CASE_UNROLL(0);
    CASE_UNROLL(1);
    CASE_UNROLL(2);
    // ...
    CASE_UNROLL(NBLOCK-1);
}

【讨论】:

  • 感谢您的提示。如果模板可行,它仍然无法回答问题。我认为不是,但我希望得到专家的回答。
【解决方案2】:

基于模板的展开:

template<int N>
struct loopUnroller
{
  template<typename Operation>
  inline void operator(Operation& op) { op(); loopUnroller<N-1>(op); }
};

template<>
struct loopUnroller<0>
{
  template<typename Operation>
  inline void operator(Operation& op) { op(); }
};

loopUnroller&lt;6&gt;(Foo) 的调用可能是内联的,但也包含对内联loopUnroller&lt;5&gt;(Foo) 的调用等。每个级别都会对Foo() 增加一个额外的调用。

如果您的编译器拒绝内联 16 层深度,有一个简单的解决方法:

template<>
struct loopUnroller<16>
{
  template<typename Operation>
  inline void operator(Operation& op) { 
        op(); op(); op(); op();
        op(); op(); op(); op();
        op(); op(); op(); op();
        op(); op(); op(); op();
  }
};

具有对数复杂度:

template<int N>
struct loopUnroller
{
  template<typename Operation>
  inline void operator(Operation& op) { 
       loopUnroller<N/2>(op);
       loopUnroller<N/2>(op);
       if (N%1) { op(); } // Will be optimized out if N is even.
  }
};

具有动态复杂性:

template<int L>
struct loopUnroller
{
  template<typename Operation>
  inline void operator(Operation& op, int i) {
     if (i & (1<<L)) {
       for(int j = 0; j != 1<<L; ++j)
       {
         op();
       }
     }
     loopUnroller<L-1>(op, i);
  }
};

for 循环现在有一个固定的运行时长度,使其很可能被展开。 因此,您有一个长度为 32、16、8、4、2 和 1 的展开循环(假设没有专门化),并且在运行时您可以根据 i 的位选择循环。

【讨论】:

  • 要指出的是,我想确保有一个开关,因为我只在运行时知道循环的起点。
  • @hivert:这又是一个 XY 问题。你假设一个特定的解决方案。最后的 sn-p 代码也有 int i 作为运行时起点,只是没有开关。
猜你喜欢
  • 2014-07-31
  • 2016-08-03
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多