c++ - 为什么此 SSE2 代码执行不一致?

标签 c++ sse intrinsics icc

作为一项学习练习,我正在尝试在各种架构上使用 SIMD 加速矩阵乘法代码。我的 SSE2 的 3D 矩阵乘法代码有一个奇怪的问题,它的性能在两个极端之间跳跃,即 ~5ms(预期)或 ~100ms 进行 100 万次操作。

这段代码做的唯一“坏”事情是未对齐的存储/加载和最后将 vector 存储到内存中而没有第 4 个元素践踏内存的 hack。这可以解释一些性能差异,但性能差异如此之大这一事实让我怀疑我遗漏了一些重要的东西。

我已经尝试了一些东西,但我会在睡了一觉之后再尝试一下。

请参阅下面的代码。 m_matrix 变量在 16 字节边界上对齐。

void Matrix3x3::MultiplySSE2(Matrix3x3 &other, Matrix3x3 &output)
{
    __m128 a_row, r_row;
    __m128 a1_row, r1_row;
    __m128 a2_row, r2_row;

    const __m128 b_row0 = _mm_load_ps(&other.m_matrix[0]);
    const __m128 b_row1 = _mm_loadu_ps(&other.m_matrix[3]);
    const __m128 b_row2 = _mm_loadu_ps(&other.m_matrix[6]);

    // Perform dot products with first row
    a_row = _mm_set1_ps(m_matrix[0]);
    r_row = _mm_mul_ps(a_row, b_row0);
    a_row = _mm_set1_ps(m_matrix[1]);
    r_row = _mm_add_ps(_mm_mul_ps(a_row, b_row1), r_row);
    a_row = _mm_set1_ps(m_matrix[2]);
    r_row = _mm_add_ps(_mm_mul_ps(a_row, b_row2), r_row);

    _mm_store_ps(&output.m_matrix[0], r_row);

    // Perform dot products with second row
    a1_row = _mm_set1_ps(m_matrix[3]);
    r1_row = _mm_mul_ps(a1_row, b_row0);
    a1_row = _mm_set1_ps(m_matrix[4]);
    r1_row = _mm_add_ps(_mm_mul_ps(a1_row, b_row1), r1_row);
    a1_row = _mm_set1_ps(m_matrix[5]);
    r1_row = _mm_add_ps(_mm_mul_ps(a1_row, b_row2), r1_row);

    _mm_storeu_ps(&output.m_matrix[3], r1_row);

    // Perform dot products with third row
    a2_row = _mm_set1_ps(m_matrix[6]);
    r2_row = _mm_mul_ps(a2_row, b_row0);
    a2_row = _mm_set1_ps(m_matrix[7]);
    r2_row = _mm_add_ps(_mm_mul_ps(a2_row, b_row1), r2_row);
    a2_row = _mm_set1_ps(m_matrix[8]);
    r2_row = _mm_add_ps(_mm_mul_ps(a2_row, b_row2), r2_row);

    // Store only the first 3 elements in a vector so we dont trample memory
    _mm_store_ss(&output.m_matrix[6], _mm_shuffle_ps(r2_row, r2_row,        _MM_SHUFFLE(0, 0, 0, 0)));
    _mm_store_ss(&output.m_matrix[7], _mm_shuffle_ps(r2_row, r2_row, _MM_SHUFFLE(1, 1, 1, 1)));
    _mm_store_ss(&output.m_matrix[8], _mm_shuffle_ps(r2_row, r2_row, _MM_SHUFFLE(2, 2, 2, 2)));
}

最佳答案

这样的性能影响听起来您的数据有时可能会越过页面行,而不仅仅是缓存行。如果您在包含许多不同矩阵的缓冲区上进行测试,而不是重复测试同一个小矩阵,那么可能在另一个 CPU 内核上运行的其他东西正在将您的缓冲区推出 L3?

代码中的性能问题(不能解释 20 倍方差。这些应该总是很慢):

_mm_set1_ps(m_matrix[3]) 等等会出问题。它需要一个 pshufdmovaps + shufps 来广播一个元素。不过,我认为这对于 matmuls 来说是不可避免的。

存储最后 3 个元素而不写入末尾:尝试 PALIGNR 将前一行的最后一个元素放入包含最后一行的 reg 中。然后你可以做一个单独的未对齐的商店,它与前面的商店重叠。这比 movss/extractps/extractps 快得多。

如果你想尝试使用更少的未对齐 16B 存储,请尝试 movss,随机播放或右移 4 个字节(psrldq 又名 _mm_bsrli_si128), 然后 movqmovsd 一次性存储最后 8 个字节。 (与按元素移位不同,逐字节移位与随机播放在同一执行端口上)

你为什么做三个 _mm_shuffle_ps (shufps)?对于最后一行的第一列,低元素已经是您想要的元素。无论如何,我认为 extractps 比 shuffle + store 更快,在非 AVX 上,在非 AVX 上防止源被 shufps 破坏需要采取行动。 pshufd 会起作用。)

关于c++ - 为什么此 SSE2 代码执行不一致?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/31502826/

相关文章:

c++ - 我不明白使用sse的代码在哪里有问题

linux - Fortran 内部函数 sqrt() 行为

c++ - 以任意顺序加入多个线程

c - SIMD (AVX) 比较

c++ - C++ 中的自定义异常

windows - 为什么 Windows x64 调用约定不使用 XMM 寄存器来传递超过 4 个整数参数?

c - _mm256_shuffle_ps 是如何工作的?

arm - ARM Neon 内在函数有很好的引用吗?

c++ - 即使在使用 extern "C"之后,如何解决名称重整出现的问题?

c++ - C++中快速搜索的数据结构