【问题标题】:How to vectorize a distance calculation using SSE2如何使用 SSE2 向量化距离计算
【发布时间】:2013-06-04 17:26:19
【问题描述】:

A 和 B 是向量或长度 N,其中 N 可以在 20 到 200 的范围内。 我想计算这些向量之间距离的平方, 即 d^2 = ||A-B||^2.

到目前为止我有:

float* a = ...;
float* b = ...;
float d2 = 0;

for(int k = 0; k < N; ++k)
{
    float d = a[k] - b[k];
    d2 += d * d;
}

这似乎工作正常,除了我已经分析了我的代码并且这是瓶颈(超过 50% 的时间都花在了这样做上)。 我在 Win 7 上使用 Visual Studio 2012,并带有以下优化选项:/O2 /Oi /Ot /Oy-。 我的理解是 VS2012 应该自动矢量化该循环(使用 SSE2)。 但是,如果我在代码中插入#pragma loop(no_vector),我不会明显减速,所以我猜循环没有被矢量化。编译器通过以下消息确认:

  info C5002: loop not vectorized due to reason '1105'

我的问题是:

  1. 是否可以修复此代码以便 VS2012 可以对其进行矢量化处理?
  2. 如果不是,尝试自己对代码进行矢量化是否有意义?
  3. 能否推荐一个网站让我了解 SSE2 编码?
  4. 是否存在某个 N 值,低于该值矢量化会适得其反?
  5. 什么是reason '1105'

【问题讨论】:

    标签: c++ visual-c++ optimization vectorization sse2


    【解决方案1】:

    使用 SSE 内在函数实现这一点非常简单:

    #include "pmmintrin.h"
    
    __m128 vd2 = _mm_set1_ps(0.0f);
    float d2 = 0.0f;
    int k;
    
    // process 4 elements per iteration
    for (k = 0; k < N - 3; k += 4)
    {
        __m128 va = _mm_loadu_ps(&a[k]);
        __m128 vb = _mm_loadu_ps(&b[k]);
        __m128 vd = _mm_sub_ps(va, vb);
        vd = _mm_mul_ps(vd, vd);
        vd2 = _mm_add_ps(vd2, vd);
    }
    
    // horizontal sum of 4 partial dot products
    vd2 = _mm_hadd_ps(vd2, vd2);
    vd2 = _mm_hadd_ps(vd2, vd2);
    _mm_store_ss(&d2, vd2);
    
    // clean up any remaining elements
    for ( ; k < N; ++k)
    {
        float d = a[k] - b[k];
        d2 += d * d;
    }
    

    请注意,如果您能保证 ab 是 16 字节对齐的,那么您可以使用 _mm_load_ps 而不是 _mm_loadu_ps,这可能有助于提高性能,尤其是在较旧的(Nehalem 之前的)CPU 上。

    另请注意,对于这样的循环,相对于负载数量而言,算术指令非常少,那么性能很可能会受到内存带宽的限制,并且在实践中可能无法实现矢量化的预期加速。

    【讨论】:

    • 好答案!就像我自己会做的那样。但是,我建议@user2151446 确保分配ab 并对齐16 个字节。这样你就可以使用_mm_load_ps 而不是_mm_loadu_ps。这将进一步优化代码。
    • 谢谢。这工作得很好,虽然只和我使用/fp:fast option 的原始代码一样快。如果我指定/fp:fast,则将#pragma loop(no_vector) 放在第二个循环上以阻止VS 尝试对其进行矢量化并从第一个循环的矢量化中损失一半的增益是很重要的。
    • @oysteijo:谢谢 - 我现在添加了关于对齐负载的注释。
    • @user2151446:如果您的向量很大并且您受到内存带宽的限制,您可能不会从 SSE 中看到太多好处 - 我现在在答案中添加了一个注释。
    • 我通常使用#define ROUND_UP(x, s) (((x)+((s)-1)) & -(s)) #define ROUND_DOWN(x, s) ((x ) & -(s))。我使用 ROUND_UP 来分配数组,所以我不必担心超出范围,当我需要担心超出范围时我使用 ROUND_DOWN。但我认为你的方法效果很好。我的意思是你会为 8 向量做 (i = 0; i
    【解决方案2】:

    MSDN documentation 开始,1105 错误代码意味着编译器无法弄清楚如何将代码简化为矢量化指令。对于浮点运算,表明您需要指定 /fp:fast 选项以启用任何浮点缩减。

    【讨论】:

    • +1 用于 /fp:fast 选项 - 它确实会导致代码被矢量化。谢谢!但是,您的代码(即使使用 /fp:fast)实际上花费的时间是我的原始代码(没有 /fp:fast)的两倍。 new[]/delete[] 很受欢迎。用堆栈分配的缓冲区替换堆分配的 dist 减少了一半的时间,使我的执行时间回到我开始的地方。循环两次数据也很受欢迎。事实证明,最好的办法是保留我的原始代码并使用 /fp:fast 将速度提高 2 倍。
    • 另外感谢 MSDN 中关于 1105 错误代码的页面,不知道为什么我找不到它(我实际上去过那个页面!)。如果您删除代码,我将接受此答案。我的 /fp:fast 原始代码是我的首选解决方案。
    • @user2151446 我已经删除了代码,尽管在我的测试用例中,您示例中的代码没有矢量化,因此增加了复杂性。明天我会提出一个关于这个的问题。
    猜你喜欢
    • 1970-01-01
    • 2018-08-23
    • 2016-04-02
    • 1970-01-01
    • 1970-01-01
    • 2017-05-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多