使用带有 STL 向量的 SSE 计算平均值

Posted

技术标签:

【中文标题】使用带有 STL 向量的 SSE 计算平均值【英文标题】:Calculate average using SSE with STL vectors 【发布时间】:2013-07-02 11:11:14 【问题描述】:

我正在尝试学习矢量化,而不是重新设计我正在使用的***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[],但必须使用迭代器

【问题讨论】:

正如建议:也许 Thrust 库对你来说会很有趣,code.google.com/p/thrust 看起来确实很有趣 【参考方案1】:

您不必要地混合了浮点数和双精度数,尤其是当您不让累加器加倍时,您的精度将完全被破坏,并且对于更大的系列来说不会接近令人满意。

由于算法是超轻量级的,因此破坏您的性能的最有可能是内存访问,读取内存缓存行及其工作方式。基本上你需要在这里做的是提前探测,一些处理器有明确的指令来把东西拉到你的缓存中,否则你可以提前在内存位置执行加载。在您的循环中创建另一个嵌套级别,并定期使用您知道在几次迭代中将获得的数据填充缓存。

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

【讨论】:

它们是非常有用的指南;但这确实意味着我必须重组调用算法的方式以适应优化。在这种情况下,它是更大的算法交易代码的一部分。 @Ylisar,使用一个 SSE 或 AVX 寄存器通过填充额外元素来处理 3D 矢量绝对不是您想要做的。太多人在 SIMD 上犯了这个错误。使用 SIMD 的正确方法类似于标量代码。不同之处在于,例如使用 3D 向量而不是使用三个 x、y、z 标量寄存器,您使用三个 SIMD 寄存器(vx =xxxx、vy=yyyy、vz=zzzz)并一次对等于 SIMD 宽度的多个向量进行操作。这可以让您获得 100% 的效率并避免使用水平指令。 @redrum - 可能是因为将数据组织为 AoS 比 SoA 更灵活。此外,SoA 的缓存行为比 AoS 差得多。老实说,我只见过 SoA 用于非常有限的数据集,比如粒子。 @Yilsar,正确的用法是 AoSoA 或混合 SoA。您声称 SoA 比 AoS 具有“更糟糕”的缓存行为的说法是错误的。这是使用 SoA(或者更确切地说是 AoSoA)的主要原因之一——因为它对缓存更友好。 AoSoA 用于许多应用,例如在矩阵乘法和光线追踪中。

以上是关于使用带有 STL 向量的 SSE 计算平均值的主要内容,如果未能解决你的问题,请参考以下文章

计算大小增加的向量的平均值

如何从df计算每n个向量的平均值

计算向量中每 n 个值的平均值

计算具有一个输入参数的向量的平均值并找到以下向量的平均值:[1 2 3 4 5 6] [关闭]

使用 for-loop 和 if 函数创建一个新向量?

如何计算向量中的渐进平均值但在满足条件时重新启动?