用 gcc 编译时,函数
sum_of_squares
返回 0。我做错了什么还是这是 gcc bug?我知道我不会处理 n 不能被 8 整除的情况。
#include <stdio.h>
#include <x86intrin.h>
int sum_of_squares(int x[], int n) {
int sum = 0;
__m256i sum8 = _mm256_set1_epi32(0);
for (int i = 0; i < n; i += 8) {
__m256i x8 = _mm256_load_si256((__m256i *)&x[i]);
x8 = _mm256_mul_epi32(x8, x8);
sum8 = _mm256_add_epi32(x8, sum8);
}
int *_sum = (int *)&sum8;
for (int i = 0; i < 8; i++) sum += _sum[i];
return sum;
}
int main() {
_Alignas(32) int x[16];
for (int i = 0; i < 15; i++) {
x[i] = i;
}
printf("%d", sum_of_squares(x, 16));
}
将
int*
指向 __m256i
(long long
元素的 GNU C 向量)违反了严格别名规则。 因此,GCC14.2 -O2
将您的函数优化为 return 0;
。 (xor eax,eax
/ ret
).
我们可以通过使用
-fno-strict-aliasing
并看到非零结果来验证这是问题所在。 (请参阅 Godbolt )。不幸的是
-fsanitize=undefined
没有捕获此错误(或未初始化的 x[]
的最后一个元素)。return 0;
时,这通常意味着您要么返回了错误的变量,要么存在编译时可见的 UB。 (有时编译器甚至不设置返回值,甚至省略 ret
指令,因此执行会陷入接下来的情况。)
使用
_mm256_storeu_si256
到 int tmparr[8];
(或者对齐 tmp 数组并使用 _mm256_store_si256
)。
或者更好,请参阅 使用 AVX512 或 AVX2 计算所有打包 32 位整数之和的最快方法(一般来说 进行水平 SSE 向量和(或其他缩减)的最快方法) - 洗牌将低半部分向下与低半部分对齐并进行垂直 SIMD 加法,每一步减少一半的元素数量,直到你只剩下一个了。 (如果您避免使用 UB,GCC 实际上会将您的归约循环优化为一系列洗牌和添加。)
正如 Soonts 提到的,您还需要
vpmulld
(_mm256_mullo_epi32
) 非展宽 32 位乘法,而不是 vpmuldq
(_mm256_mul_epi32
) 展宽有符号乘法,后者仅读取偶数元素以产生 64 位结果。 你的严格别名错误就是你得到零的原因;解决这个问题对于获得您想要的非零结果也是必要的,而不仅仅是偶数元素(0,2等)的总和。
您还应该初始化第 16 个元素 (
x[15]
),正如 Soonts 指出的那样,您当前的代码无法做到这一点。
__attribute__((may_alias))
让您可以键入 int
的版本,您可以指向任何东西如果你真的想要,你可以使用
typedef int32_t aliasing_i32 __attribute__((may_alias))
,你可以安全地指向任何对齐 4 或更多的东西,使用它代替 int
作为你的指针类型。 (或者如果您使用 __attribute__((may_alias, aligned(1)))
,则不对齐)。
有趣的事实:GCC 和 Clang 对
__m256i
的定义使用 may_alias
,允许您将 __m256i*
指向任何东西,但反之则不然。
但这并没有什么好处;数组不占用“额外”存储空间;
__m256i
向量优化为仅YMM寄存器。 无论如何,归约循环都会优化为随机播放,但如果没有,无论哪种编写方式都会有 32 字节的堆栈空间,该空间由 vmovdqa
写入并由标量循环读取。
x8 = _mm256_mul_epi32(x8, x8);
指导并没有达到你期望的效果。它仅使用每个 64 位通道中的最低 32 位,并计算 4 个乘积,每个乘积 64 位。
要计算 8 个 32 位乘积,请考虑使用
_mm256_mullo_epi32
。
for (int i = 0; i < 15; i++)
请注意,您仅初始化 15 个数字,然后加载 16 个。
int *_sum = (int *)&sum8;
这可行,但通常会编译成缓慢的代码,矢量存储然后标量加载。
要计算向量中所有数字的水平总和,请参阅答案。
#include <stdio.h>
#include <x86intrin.h>
int sum_of_squares(int x[], int n)
{
if( 0 != (n+1)%8 )
{
// Here n needs to be multiples of 8 - 1
printf( "sum_of_squares data size parameter needs to be multiples of 8-1\n");
printf( "data size n: %d n-1: %d\n", n, n-1);
printf( "Allowed values for n: 7, 15, 23, ...\n");
return -1;
}
int sum = 0;
__m256i sum8 = _mm256_set1_epi32(0);
for (int i = 0; i < n; i += 8)
{
__m256i initVal = _mm256_load_si256((__m256i *)&x[i]);
__m256i x8 = _mm256_load_si256((__m256i *)&x[i]);
// x8 = _mm256_mul_epi32(x8, x8);
x8 = _mm256_mullo_epi32( initVal, x8);
sum8 = _mm256_add_epi32(x8, sum8);
}
int *_sum = (int *)&sum8;
for (int i = 0; i < 8; i++) sum += _sum[i];
return sum;
}
int main()
{
{
_Alignas(32) int x[8];
for (int i = 0; i < 8; i++)
{
x[i] = i;
}
printf( "sum_of_squares of 0^2+1^2+2^2+..7^2 elements: %d\n", sum_of_squares(x, 8-1));
int n = 8-1;
printf( "%d*(%d+1)*(2*%d+1)/6 = %d*%d*(%d+1)/6 = %d*%d*%d/6 = %d\n", n, n, n, n, n+1, 2*n, n, n+1, 2*n+1, n*(n+1)*(2*n+1)/6 );
}
{
_Alignas(32) int x[16];
for (int i = 0; i < 16; i++)
{
x[i] = i;
}
printf( "sum_of_squares of 0^2+1^2+2^2+..15^2 elements: %d\n", sum_of_squares(x, 16-1));
int n = 16-1;
printf( "%d*(%d+1)*(2*%d+1)/6 = %d*%d*(%d+1)/6 = %d*%d*%d/6 = %d\n", n, n, n, n, n+1, 2*n, n, n+1, 2*n+1, n*(n+1)*(2*n+1)/6 );
}
{
_Alignas(32) int x[24];
for (int i = 0; i < 24; i++)
{
x[i] = i;
}
printf( "sum_of_squares of 0^2+1^2+2^2+..23^2 elements: %d\n", sum_of_squares(x, 24-1));
int n = 24-1;
printf( "%d*(%d+1)*(2*%d+1)/6 = %d*%d*(%d+1)/6 = %d*%d*%d/6 = %d\n", n, n, n, n, n+1, 2*n, n, n+1, 2*n+1, n*(n+1)*(2*n+1)/6 );
}
{
_Alignas(32) int x[4];
for (int i = 0; i < 4; i++)
{
x[i] = i;
}
printf( "sum_of_squares of 0^2+1^2+2^2+..23^2 elements: %d\n", sum_of_squares(x, 4-1));
int n = 4-1;
printf( "%d*(%d+1)*(2*%d+1)/6 = %d*%d*(%d+1)/6 = %d*%d*%d/6 = %d\n", n, n, n, n, n+1, 2*n, n, n+1, 2*n+1, n*(n+1)*(2*n+1)/6 );
}
{
_Alignas(32) int x[13];
for (int i = 0; i < 13; i++)
{
x[i] = i;
}
printf( "sum_of_squares of 0^2+1^2+2^2+..23^2 elements: %d\n", sum_of_squares(x, 13-1));
int n = 13-1;
printf( "%d*(%d+1)*(2*%d+1)/6 = %d*%d*(%d+1)/6 = %d*%d*%d/6 = %d\n", n, n, n, n, n+1, 2*n, n, n+1, 2*n+1, n*(n+1)*(2*n+1)/6 );
}
// I feel better to use mathematical formula instead of wasting memories at RAM and process handlers.
return 0;
}
/*
$ /usr/bin/gcc.exe -mavx2 -g -Wall sum_of_squares.c -o ./a.out
$ ./a.out
sum_of_squares of 0^2+1^2+2^2+..7^2 elements: 140
7*(7+1)*(2*7+1)/6 = 7*8*(14+1)/6 = 7*8*15/6 = 140
sum_of_squares of 0^2+1^2+2^2+..15^2 elements: 1240
15*(15+1)*(2*15+1)/6 = 15*16*(30+1)/6 = 15*16*31/6 = 1240
sum_of_squares of 0^2+1^2+2^2+..23^2 elements: 4324
23*(23+1)*(2*23+1)/6 = 23*24*(46+1)/6 = 23*24*47/6 = 4324
sum_of_squares data size parameter needs to be multiples of 8-1
data size n: 3 n-1: 2
Allowed values for n: 7, 15, 23, ...
sum_of_squares of 0^2+1^2+2^2+..23^2 elements: -1
3*(3+1)*(2*3+1)/6 = 3*4*(6+1)/6 = 3*4*7/6 = 14
sum_of_squares data size parameter needs to be multiples of 8-1
data size n: 12 n-1: 11
Allowed values for n: 7, 15, 23, ...
sum_of_squares of 0^2+1^2+2^2+..23^2 elements: -1
12*(12+1)*(2*12+1)/6 = 12*13*(24+1)/6 = 12*13*25/6 = 650
*/