每个人。
我的问题是如果我有如下三个数组
float a[7] = {1.0, 2.0, 3.0, 4.0,
5.0, 6.0, 7.0};
float b[7] = {2.0, 2.0, 2.0, 2.0,
2.0, 2.0, 2.0};
float c[7] = {0.0, 0.0, 0.0, 0.0,
0.0, 0.0, 0.0};
我想按如下方式执行逐元素乘法运算
c[i] = a[i] * b[i], i = 0, 1, ..., 6
对于前四个元素,我可以如下使用 SSE 内在函数
__m128* sse_a = (__m128*) &a[0];
__m128* sse_b = (__m128*) &b[0];
__m128* sse_c = (__m128*) &c[0];
*sse_c = _mm_mul_ps(*sse_a, *sse_b);
c中的内容将是
c[0] = 2.0, c[1] = 4.0, c[2] = 6.0, c[3] = 8.0
c[4] = 0.0, c[5] = 0.0, c[6] = 0.0
索引 4、5 和 6 中剩余的三个数字,我使用以下代码 执行逐元素乘法运算
sse_a = (__m128*) &a[4];
sse_b = (__m128*) &b[4];
sse_c = (__m128*) &c[4];
float mask[4] = {1.0, 1.0, 1.0, 0.0};
__m128* sse_mask = (__m128*) &mask[0];
*sse_c = _mm_add_ps( *sse_c,
_mm_mul_ps( _mm_mul_ps(*sse_a, *sse_b), *sse_mask ) );
c[4-6]中的内容将是
c[4] = 10.0, c[5] = 12.0, c[6] = 14.0, which is the expected result.
_mm_add_ps() 并行添加四个 float ,第一、第二、第三个 float 分别分配在数组a、b、c的索引4、5、6处。 但是第四个 float 没有分配给数组。 为了避免无效的内存访问,我乘以 sse_mask 使第四个数字为零,然后将结果添加回 sse_c(数组 c)。
但我想知道它是否安全?
非常感谢。
最佳答案
你似乎有正确的数学运算,但我真的不确定像你这样使用强制转换是否是在 __m128
变量中加载和存储数据的方法。
加载和存储
要将数组中的数据加载到 __m128
变量,您应该使用 __m128 _mm_load_ps (float const* mem_addr)
或 __m128 _mm_loadu_ps (float const*内存地址)
。很容易弄清楚这里是什么,但需要一些精确度:
- 对于涉及内存访问或操作的操作,通常有两个函数做同样的事情,例如
load
和loadu
。第一个要求你的内存在 16 字节边界上对齐,而u
版本没有这个要求。如果您不了解内存对齐,请使用u
版本。 - 您还有
load_ps
和load_pd
。区别:s
代表单精度(好旧的float
),d
代表 double ,即 double 。当然,每个__m128
变量只能放置两个 double ,但可以放置 4 个 float 。
因此从数组加载数据非常简单,只需执行:__m128* sse_a = _mm_loadu_ps(&a[0]);
。对 b 做同样的事情,但对 c 来说,这真的取决于。如果你只想把乘法的结果放在里面,把它初始化为0是没有用的,加载它,然后把乘法的结果加进去,最后再取回来。
您应该使用load
的挂起操作来存储void _mm_storeu_ps (float* mem_addr, __m128 a)
的数据。因此,一旦乘法完成并且结果在 sse_c
中,只需执行 _mm_storeu_ps(&c[0@, sse_c) ;
算法
使用掩码背后的想法很好,但你有更简单的事情:从 a[3]
加载和存储数据(b 和 c 相同)。这样一来,它就会有 4 个元素,那么就不需要使用任何 mask 了?是的,已经对第三个元素进行了一项操作,但这将是完全透明的:store
操作只会用新值替换旧值。因为两者是平等的,所以这不是问题。
一种替代方法是在你的数组中存储 8 个元素,即使你只需要 7 个元素。这样你就不必担心内存是否被分配,不需要像上面那样的特殊逻辑来支付 3 个 float 的成本,这在所有最近的计算机上都没有。
关于c++ - SSE作用于元素个数不是4的倍数的数组,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/39384037/