使用模板元编程展开循环的不同方法的优缺点
Pros/cons of different methods of loop unrolling using template metaprogramming
我对编译时循环展开的一般解决方案很感兴趣(我在SIMD设置中使用它,其中每个函数调用占用特定数量的时钟周期,并且可以并行执行多个调用,因此我需要调整累加器的数量以最小化浪费的周期——添加额外的累加器和手动展开会产生显着的改进,但很费力)。
理想情况下,我希望能够写像
这样的东西unroll<N>(f,args...); // with f a pre-defined function
unroll<N>([](...) { ... },args...); // using a lambda
并生成以下内容:
f(1,args...);
f(2,args...);
...
f(N,args...);
到目前为止,我有三种不同的模板元程序解决方案,我想知道不同方法的优点/缺点是什么,特别是关于编译器如何内联函数调用。
方法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 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
方法2(递归构造函数)
template <int N, typename F, typename ...Args>
struct unroll_c {
unroll_c(F&& f, Args&&... args) {
unroll_c<N-1,F,Args...>(std::forward<F>(f),std::forward<Args>(args)...);
f(N,args...);
};
};
template <typename F, typename ...Args>
struct unroll_c<1,F,Args...> {
unroll_c(F&& f, Args&&... args) {
f(1,args...);
};
};
调用语法非常难看:
unroll_c<10,decltype(mult)&,int&>(mult,x);
unroll_c<10,decltype(mult)&,int&>(mult,2); // doesn't compile
如果使用匿名lambda,则必须显式指定和函数的类型,这很尴尬。
方法3(递归静态成员函数)
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);
}
相关文章:
- 如何循环打印顶点结构
- 如何在C++中从两个单独的for循环中添加两个数组
- C++我的数学有什么问题,为什么我的代码不能正确循环
- 正在尝试了解输入验证循环
- std::map<struct,struct>::find 找不到匹配项,但是如果我循环通过 begin() 到 end(),我在那里看到匹配项
- 循环后如何继续阅读
- Ardunio UNO解决了多个重叠的定时器循环
- Eigen如何在容器循环中干净地附加矩阵
- 在某些循环内使用vector.push_back时出现分段错误
- 我正在使用嵌套的while循环来解析具有多行的文本文件,但由于某种原因,它只通过第一行,我不知道为什么
- 为什么我的for循环不能正确获取argv
- 如何声明特征矩阵,然后通过嵌套循环初始化它
- while循环中while循环的时间复杂度是多少
- C++中的高效循环缓冲区,它将被传递给C样式数组函数参数
- 为什么在这个代码结束循环中没有得到结束
- 在基于范围的for循环中使用结构化绑定声明
- 用于C++中带有数组和指针的循环
- 循环中的随机函数
- 是什么阻止DOMTimerCoordinator::NextID进入无休止的循环
- 使用模板元编程展开循环的不同方法的优缺点