这是 gcc 错误吗?当在 __m256i

问题描述 投票:0回答:3

用 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));
}
c gcc x86 intrinsics avx
3个回答
0
投票

int*
指向
__m256i
long long
元素的 GNU C 向量)违反了严格别名规则。 因此,GCC14.2
-O2
将您的函数优化为
return 0;
。 (
xor eax,eax
/
ret
).

我们可以通过使用

-fno-strict-aliasing
并看到非零结果来验证这是问题所在。 (请参阅 Godbolt )。
MSVC 始终允许所有别名,并且它并不总是在 GCC 或 Clang 上中断,因此有些人错误地认为像您这样的代码是安全的。 事实并非如此,正如本例所示。

不幸的是

-fsanitize=undefined
没有捕获此错误(或未初始化的
x[]
的最后一个元素)。
但一般来说,当您检查 asm 并看到您的函数优化为
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
写入并由标量循环读取。


-1
投票

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;

这可行,但通常会编译成缓慢的代码,矢量存储然后标量加载。
要计算向量中所有数字的水平总和,请参阅答案


-3
投票
#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
*/
© www.soinside.com 2019 - 2024. All rights reserved.