我正在尝试学习矢量化,而不是重新使用我正在使用的轮子Agner Fog's vector library
这是我原来的C ++ / STL代码
#include <vector>
#include <vectorclass.h>
template<typename T>
double mean_v1(T begin,T end) {
float mean = 0;
std::for_each(begin,end,[&mean](const double& d) { mean+=d; });
return mean / std::distance(begin,end);
}
double mean_v2(T begin,T end) {
float mean = 0;
const int distance = std::distance(begin,end); // This is expensive
const int loop = ( distance >> 2)+1; // divide by 4
const int partial = distance & 2; // remainder 4
Vec4d vec;
for(int i = 0; i < loop;++i) {
if(i == (loop-1)) {
vec.load_partial(partial,&*begin);
mean = horizontal_add(vec);
}
else {
vec.load(&*begin);
mean = horizontal_add(vec);
begin+=4; // This is expensive
}
}
return mean / distance;
}
int main(int argc,char**argv) {
using namespace boost::assign;
std::vector<float> numbers;
// Note 13 numbers, which won't fit into a sse register perfectly
numbers+=39.57,39.57,39.604,39.58,39.61,31.669,31.669,31.669,31.65,32.09,33.54,32.46,33.45;
const float mean1 = mean_v1(numbers.begin(),numbers.end());
const float mean2 = mean_v2(numbers.begin(),numbers.end());
return 0;
}
v1和v2都能正常工作,它们都需要大约相同的时间。然而,分析它显示std :: distance()并且移动迭代器几乎占总时间的45%。向量加法仅为0.8%,明显快于v1。
在网上搜索,所有示例似乎都处理了完全适合SSE寄存器的完美数值。人们如何处理奇数个值,例如在这个例子中,设置循环所需的时间比计算时间长。
我认为必须有关于如何处理这种情况的最佳实践或想法。
假设我无法更改mean()的接口以获取float [],但必须使用迭代器
答案 0 :(得分:3)
你正在混合浮动和放大器不必要地加倍,特别是当你不让你的累加器加倍时你的精度完全被破坏,并且对于较大的系列来说不会接近令人满意。
由于算法超重,所以在这里破坏你的性能最有可能是内存访问,读取内存缓存行以及它们的工作方式。基本上你需要做的是提前探测,一些处理器有明确的指令将内容拉入你的缓存,否则你可以提前在内存位置执行加载。在循环中创建另一级嵌套,并定期使用您将在几次迭代中获得的数据填充缓存。
人们为最大限度地提高性能所做的是他们花了很多时间来设计他们的数据布局。您不需要对数据进行中间转换。所以人们做的是他们分配对齐的内存(大多数SIMD指令集要么对读取/写入未对齐的内存要求或施加严重的惩罚),然后他们尝试以适合指令集的方式聚合数据。事实上,将数据填充到指令集支持的任何寄存器大小通常都是一种胜利。因此,如果我们假设您要处理3维向量,那么使用未使用的额外元素进行填充几乎总是一个很大的胜利。