使用SSE intrinsic将4个点积存储到C中的连续数组中的最有效方法

使用SSE intrinsic将4个点积存储到C中的连续数组中的最有效方法,c,sse,simd,intrinsics,dot-product,C,Sse,Simd,Intrinsics,Dot Product,我正在使用SSE内部函数优化英特尔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,

我正在使用SSE内部函数优化英特尔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=0-R1-0-0

tmp2=0-0-R2-0

tmp3=0-0-0-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个值的地址):

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


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

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

这在光线跟踪中经常使用,一次针对一个平面测试4条光线(例如,在遍历kd树时)。但是,如果您无法控制输入数据,那么转置的开销可能不值得。代码也将在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\u add\u ps
现在完全独立。另外,我不知道添加和洗牌的相对时间,但这可能会稍微快一点



希望有帮助。

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


专业的方法是celion的转置方法。MSVC的宏将为您进行转置。

也可以使用SSE3 hadd。在一些琐碎的测试中,它比使用dotps更快。 这将返回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];
}

我知道这个问题很老了,但为什么要使用
\u mm\u add\u ps
?替换为:

tmp0 = _mm_or_ps(tmp0, tmp1);
tmp2 = _mm_or_ps(tmp2, tmp3);
tmp0 = _mm_or_ps(tmp0, tmp2);
您可能会隐藏一些
\u mm\u dp\u ps
延迟。第一个
\u mm\u或\u ps
也不等待最后的2点积,它是一个(快速)逐位操作。最后:

_mm_storeu_ps(C_2, _mm_add_ps(tmp0, C_0));

在商店开张之前,您仍然需要将旧值(C_0n)添加到每个dot产品中。它们都是独立的,所以速度可能不会太慢,但也不会太漂亮:)
_mm_storeu_ps(C_2, _mm_add_ps(tmp0, C_0));