C++ 使用模板元编程的不同循环展开方法的优缺点

C++ 使用模板元编程的不同循环展开方法的优缺点,c++,metaprogramming,loop-unrolling,C++,Metaprogramming,Loop Unrolling,我对编译时循环展开的一般解决方案感兴趣(我在SIMD设置中使用它,其中每个函数调用占用特定数量的时钟周期,并且可以并行执行多个调用,因此我需要调整累加器的数量以最大限度地减少浪费的周期——添加额外累加器和手动展开会产生显著的改进,但这很费劲) 理想的情况下,我希望能够写像这样的东西 unroll<N>(f,args...); // with f a pre-defined function unroll<N>([](...) { ... },args...); // us

我对编译时循环展开的一般解决方案感兴趣(我在SIMD设置中使用它,其中每个函数调用占用特定数量的时钟周期,并且可以并行执行多个调用,因此我需要调整累加器的数量以最大限度地减少浪费的周期——添加额外累加器和手动展开会产生显著的改进,但这很费劲)

理想的情况下,我希望能够写像这样的东西

unroll<N>(f,args...); // with f a pre-defined function
unroll<N>([](...) { ... },args...); // using a lambda
到目前为止,我有三种不同的模板元程序解决方案,我想知道不同方法的优点/缺点是什么,特别是关于编译器如何内联函数调用

方法1(递归函数)

template <int N> struct _int{ };

template <int N, typename F, typename ...Args>
inline void unroll_f(_int<N>, F&& f, Args&&... args) {      
    unroll_f(_int<N-1>(),std::forward<F>(f),std::forward<Args>(args)...);
    f(N,args...);
}
template <typename F, typename ...Args>
inline void unroll_f(_int<1>, F&& f, Args&&... args) {
    f(1,args...);
}
模板结构_int{};
模板
内联void展开f(_int,f&&f,Args&&…Args){
展开f(_int(),std::forward(f),std::forward(args)…);
f(N,args…);
}
模板
内联void展开函数(_int,f&&f,Args&&…Args){
f(1,args…);
}
调用语法示例:

int x = 2;
auto mult = [](int n,int x) { std::cout << n*x << " "; };

unroll_f(_int<10>(),mult,x); // also works with anonymous lambda
unroll_f(_int<10>(),mult,2); // same syntax when argument is temporary 
unroll_s<10>::apply(mult,x);
unroll_s<10>::apply(mult,2); 
intx=2;

自动多重=[](整数n,整数x){std::cout首先,编译器往往非常清楚何时适合展开循环。也就是说,我不建议显式展开循环。另一方面,索引可以用作类型映射的索引,在这种情况下,有必要展开内容以生成具有不同类型的版本

不过,我个人的方法是避免递归,而是通过索引扩展处理展开。下面是一个版本的简单演示,该版本被很好地调用和使用。传递参数数量的相同技术可以与递归方法一起使用,如您的示例中所示。我认为符号更可取:

#include <iostream>
#include <utility>
#include <initializer_list>

template <typename T> struct unroll_helper;
template <std::size_t... I>
struct unroll_helper<std::integer_sequence<std::size_t, I...> > {
    template <typename F, typename... Args>
    static void call(F&& fun, Args&&... args) {
        std::initializer_list<int>{(fun(I, args...), 0)...};
    }
};

template <int N, typename F, typename... Args>
void unroll(F&& fun, Args&&... args)
{
    unroll_helper<std::make_index_sequence<N> >::call(std::forward<F>(fun), std::forward<Args>(args)...);
}

void print(int index, int arg) {
    std::cout << "print(" << index << ", " << arg << ")\n";
}

int main()
{
    unroll<3>(&print, 17);
}
#包括
#包括
#包括
模板结构展开辅助程序;
模板
结构展开辅助对象{
模板
静态void调用(F&&fun、Args&&…Args){
std::初始值设定项{(fun(I,args…,0)…};
}
};
模板
无效展开(F&&fun,Args&&Args)
{
展开helper::call(std::forward(fun)、std::forward(args)…);
}
无效打印(整数索引,整数参数){

std::cout大多数现代编译器都可以并且将在他们认为有用的地方实现循环展开。不要试图智胜编译器,只需编写正确、可读的代码,让编译器优化自己的工作。优点和缺点大部分是基于意见的,因此这个问题是离题的。@CoryKramer:至少在分析显示必要之前是这样尝试应用这种“优化”的可行性和有效性。也只是为了覆盖远低于1%的特殊情况。@CoryKramer完全有效的循环展开无法执行(通常)由编译器执行,除非它在编译时知道N的值。如果您在编译时知道N的值,您可以在常规std::数组上使用for循环。编译器知道数组的大小,它将进行适当的展开。通常完全展开不是一个好主意,一旦通过某个展开因子,代码膨胀就会超过保存的分支。@m.s.:这是生成解决方案的剩余部分。我将删除它。
unroll_c<10,decltype(mult)&,int&>(mult,x); 
unroll_c<10,decltype(mult)&,int&>(mult,2); // doesn't compile
template <int N>
struct unroll_s {
    template <typename F, typename ...Args>
    static inline void apply(F&& f, Args&&... args) {
        unroll_s<N-1>::apply(std::forward<F>(f),std::forward<Args>(args)...);        
        f(N,args...);
    }
    // can't use static operator() instead of 'apply'
};
template <>
struct unroll_s<1> {
    template <typename F, typename ...Args>
    static inline void apply(F&& f, Args&&... args) {
        f(1,std::forward<Args>(args)...);
    }
};
unroll_s<10>::apply(mult,x);
unroll_s<10>::apply(mult,2); 
#include <iostream>
#include <utility>
#include <initializer_list>

template <typename T> struct unroll_helper;
template <std::size_t... I>
struct unroll_helper<std::integer_sequence<std::size_t, I...> > {
    template <typename F, typename... Args>
    static void call(F&& fun, Args&&... args) {
        std::initializer_list<int>{(fun(I, args...), 0)...};
    }
};

template <int N, typename F, typename... Args>
void unroll(F&& fun, Args&&... args)
{
    unroll_helper<std::make_index_sequence<N> >::call(std::forward<F>(fun), std::forward<Args>(args)...);
}

void print(int index, int arg) {
    std::cout << "print(" << index << ", " << arg << ")\n";
}

int main()
{
    unroll<3>(&print, 17);
}