SSE作用于元素个数不是4的倍数的数组
SSE works on the array that the number of the elements is not the multiple of four
大家。
我的问题是如果我有如下三个数组
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() 并行相加四个浮点数,第一个、第二个、第三个浮点数分配在数组a、b、c的索引4、5、6分别。
但是第四个浮点数没有分配给数组。
为了避免无效的内存访问,我乘以 sse_mask 使第四个数字为零,然后将结果添加回 sse_c(数组 c)。
但是我想知道它是否安全?
非常感谢。
您的数学运算似乎是正确的,但我真的不确定像您这样使用强制转换是否是在 __m128
变量中加载和存储数据的方法。
加载和存储
要将数据从数组加载到 __m128
变量,您应该使用 __m128 _mm_load_ps (float const* mem_addr)
或 __m128 _mm_loadu_ps (float const* mem_addr)
。很容易弄清楚这里是什么,但需要一些精确度:
- 对于涉及内存访问或操作的操作,通常有两个函数做同样的事情,例如
load
和 loadu
。第一个要求你的内存在 16 字节边界上对齐,而 u
版本没有这个要求。如果您不知道内存对齐,请使用 u
版本。
- 您还有
load_ps
和 load_pd
。区别:s
代表单精度(好旧的 float
),d
代表双精度,即双精度。当然,每个 __m128
变量只能放两个双精度数,但是 4 个浮点数。
因此从数组加载数据非常简单,只需执行:__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 个元素,那么就不需要使用任何遮罩了?是的,已经对第三个元素进行了一项操作,但这将是完全透明的:store
操作只会用新值替换旧值。因为两者是平等的,所以这不是问题。
一种替代方法是在你的数组中存储 8 个元素,即使你只需要 7 个元素。这样你就不必担心内存是否被分配,不需要像上面那样的特殊逻辑来支付 3 个的成本浮动,这在所有最近的计算机上都没有。
大家。
我的问题是如果我有如下三个数组
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() 并行相加四个浮点数,第一个、第二个、第三个浮点数分配在数组a、b、c的索引4、5、6分别。 但是第四个浮点数没有分配给数组。 为了避免无效的内存访问,我乘以 sse_mask 使第四个数字为零,然后将结果添加回 sse_c(数组 c)。
但是我想知道它是否安全?
非常感谢。
您的数学运算似乎是正确的,但我真的不确定像您这样使用强制转换是否是在 __m128
变量中加载和存储数据的方法。
加载和存储
要将数据从数组加载到 __m128
变量,您应该使用 __m128 _mm_load_ps (float const* mem_addr)
或 __m128 _mm_loadu_ps (float const* mem_addr)
。很容易弄清楚这里是什么,但需要一些精确度:
- 对于涉及内存访问或操作的操作,通常有两个函数做同样的事情,例如
load
和loadu
。第一个要求你的内存在 16 字节边界上对齐,而u
版本没有这个要求。如果您不知道内存对齐,请使用u
版本。 - 您还有
load_ps
和load_pd
。区别:s
代表单精度(好旧的float
),d
代表双精度,即双精度。当然,每个__m128
变量只能放两个双精度数,但是 4 个浮点数。
因此从数组加载数据非常简单,只需执行:__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 个元素,那么就不需要使用任何遮罩了?是的,已经对第三个元素进行了一项操作,但这将是完全透明的:store
操作只会用新值替换旧值。因为两者是平等的,所以这不是问题。
一种替代方法是在你的数组中存储 8 个元素,即使你只需要 7 个元素。这样你就不必担心内存是否被分配,不需要像上面那样的特殊逻辑来支付 3 个的成本浮动,这在所有最近的计算机上都没有。