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

时间:2015-07-19 15:28:47

标签: c++ sse intrinsics icc

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

唯一的事情"糟糕"这段代码正在做的是未对齐的存储/加载和最后的hack,以便在没有第4个元素践踏内存的情况下将向量存储到内存中。这可以解释一些性能差异,但是性能差异如此之大的事实让我怀疑我错过了一些重要的东西。

我已经尝试了几件事,但是在我睡了一会儿之后我又会有另一个问题。

见下面的代码。 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)));
}

1 个答案:

答案 0 :(得分:2)

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

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

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

存储最后3个元素而不写入结尾:尝试PALIGNR将前一行的最后一个元素放入最后一行的reg中。然后,您可以执行单个未对齐的存储,该存储与前面的存储重叠。这个shuffle要少得多,并且可能比movss / extractps / extractps更快。

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

你为什么要做 _mm_shuffle_psshufps)?对于最后一行的第一列,low元素已经是您想要的元素。无论如何,我认为extractps比非随机存储更快,而非AVX,其中保留源免受被shufps破坏的移动。 pshufd可行。)