11

我正在使用 SSE 内在函数为 Intel x86 Nehalem 微架构优化一些代码。

我的程序的一部分计算 4 个点积,并将每个结果添加到数组的连续块中的先前值。进一步来说,

tmp0 = _mm_dp_ps(A_0m, B_0m, 0xF1);
tmp1 = _mm_dp_ps(A_1m, B_0m, 0xF2);
tmp2 = _mm_dp_ps(A_2m, B_0m, 0xF4);
tmp3 = _mm_dp_ps(A_3m, B_0m, 0xF8);

tmp0 = _mm_add_ps(tmp0, tmp1);
tmp0 = _mm_add_ps(tmp0, tmp2);
tmp0 = _mm_add_ps(tmp0, tmp3);
tmp0 = _mm_add_ps(tmp0, C_0n);

_mm_storeu_ps(C_2, tmp0);

请注意,我将使用 4 个临时 xmm 寄存器来保存每个点积的结果。在每个 xmm 寄存器中,相对于其他临时 xmm 寄存器,结果被放入一个唯一的 32 位中,因此最终结果如下所示:

tmp0= R0-零-零-零

tmp1= 零-R1-零-零

tmp2= 零-零-R2-零

tmp3=零-零-零-R3

我将每个 tmp 变量中包含的值合并为一个 xmm 变量,方法是使用以下指令将它们相加:

tmp0 = _mm_add_ps(tmp0, tmp1);
tmp0 = _mm_add_ps(tmp0, tmp2);
tmp0 = _mm_add_ps(tmp0, tmp3);

最后,我将包含点积的所有 4 个结果的寄存器添加到数组的连续部分,以便数组的索引按点积递增,就像这样(C_0n 是数组中当前要更新的 4 个值; C_2 是指向这4个值的地址):

tmp0 = _mm_add_ps(tmp0, C_0n);
_mm_storeu_ps(C_2, tmp0);

我想知道是否有一种不那么迂回、更有效的方法来获取点积的结果并将它们添加到数组的连续块中。这样,我在其中只有 1 个非零值的寄存器之间进行了 3 次加法。似乎应该有一种更有效的方法来解决这个问题。

我感谢所有帮助。谢谢你。

4

4 回答 4

6

对于这样的代码,我喜欢存储 A 和 B 的“转置”,以便 {A_0m.x, A_1m.x, A_2m.x, A_3m.x} 存储在一个向量中,等等。然后你可以做仅使用乘法和加法的点积,完成后,您将所有 4 个点积都放在一个向量中,而无需任何改组。

这在光线追踪中经常使用,一次针对一个平面测试 4 条光线(例如,在遍历 kd-tree 时)。但是,如果您无法控制输入数据,则进行转置的开销可能不值得。该代码也将在 SSE4 之前的机器上运行,尽管这可能不是问题。


关于现有代码的一个小的效率说明:而不是这个

tmp0 = _mm_add_ps(tmp0, tmp1);
tmp0 = _mm_add_ps(tmp0, tmp2);
tmp0 = _mm_add_ps(tmp0, tmp3);
tmp0 = _mm_add_ps(tmp0, C_0n);

这样做可能会更好一些:

tmp0 = _mm_add_ps(tmp0, tmp1);  // 0 + 1 -> 0
tmp2 = _mm_add_ps(tmp2, tmp3);  // 2 + 3 -> 2
tmp0 = _mm_add_ps(tmp0, tmp2);  // 0 + 2 -> 0
tmp0 = _mm_add_ps(tmp0, C_0n);

由于前两个mm_add_ps现在完全独立。另外,我不知道添加与改组的相对时间,但这可能会稍微快一些。


希望有帮助。

于 2010-11-13T09:12:49.517 回答
3

也可以使用 SSE3 hadd。在一些琐碎的测试中,它比使用 _dot_ps 更快。这将返回 4 个可以添加的点积。

static inline __m128 dot_p(const __m128 x, const __m128 y[4])
{
   __m128 z[4];

   z[0] = x * y[0];
   z[1] = x * y[1];
   z[2] = x * y[2];
   z[3] = x * y[3];
   z[0] = _mm_hadd_ps(z[0], z[1]);
   z[2] = _mm_hadd_ps(z[2], z[3]);
   z[0] = _mm_hadd_ps(z[0], z[2]);

   return z[0];
}
于 2010-12-17T11:03:40.367 回答
1

您可以尝试将点积结果保留在低位字中,并使用标量存储操作_mm_store_ss将每个 m128 寄存器中的一个浮点数保存到数组的适当位置。Nehalem 的存储缓冲区应该在同一行累积连续写入,并分批刷新到 L1。

做到这一点的专业方法是 celion 的转置方法。MSVC 的_MM_TRANSPOSE4_PS宏将为您进行转置。

于 2010-11-13T09:29:01.140 回答
1

我意识到这个问题很老了,但为什么要使用_mm_add_ps呢?将其替换为:

tmp0 = _mm_or_ps(tmp0, tmp1);
tmp2 = _mm_or_ps(tmp2, tmp3);
tmp0 = _mm_or_ps(tmp0, tmp2);

您可能可以隐藏一些_mm_dp_ps延迟。第一个_mm_or_ps也不等待最后的 2 点积,它是一个(快速)按位操作。最后:

_mm_storeu_ps(C_2, _mm_add_ps(tmp0, C_0));
于 2012-12-22T00:41:44.000 回答