使用SSE和STL矢量计算平均值

Calculate average using SSE with STL vectors

本文关键字:计算 平均值 STL SSE 使用      更新时间:2023-10-16

我正在尝试学习矢量化,而不是重新检查轮子,我正在使用Agner Fog的矢量库

这是我的原始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[],但必须使用迭代器

您正在混合float&加倍是不必要的,尤其是当你不让你的累加器加倍时,你的精度会被完全破坏,对于更大的系列来说,这不会令人满意。

由于算法的重量非常轻,在这里破坏性能的很可能是内存访问、对内存缓存线的读取以及它们的工作方式。基本上,您在这里需要做的是提前探测,一些处理器有明确的指令将内容拉入缓存,否则您可以提前在内存位置执行加载。在循环中创建另一个级别的嵌套,并定期用您知道在几次迭代中会得到的数据填充缓存。

为了最大限度地提高性能,人们需要花费大量时间来实际设计数据布局。您不需要对数据进行中间转换。因此,人们所做的是分配对齐的内存(大多数SIMD指令集要求或对读取/写入未对齐的内存施加严重惩罚),然后他们试图以适合指令集的方式聚合数据。事实上,将数据填充到指令集支持的任何寄存器大小通常都是一种胜利。所以,如果假设你要处理三维向量,那么用一个未使用的额外元素填充几乎总是一个巨大的胜利。