我是汇编代码和 SSE/AVX 指令的新手。现在,我想为256位YMM寄存器中的所有位置分配一个特定的值,但我不知道最终结果是否正确。
- 要将 0 或 1 分配给
ymm0
:
__asm__ __volatile__(
"vpxor %%ymm0, %%ymm0, %%ymm0\n\t" // all are 0
or
"VPCMPEQB %%ymm0, %%ymm0, %%ymm0\n\t" // all are 1
: : :);
GDB 结果表明:
// all are 0
ymm0
{v8_float = {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0},
v4_double = {0x0, 0x0, 0x0, 0x0},
v32_int8 = {0x0 <repeats 32 times>},
v16_int16 = {0x0 <repeats 16 times>},
v8_int32 = {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0},
v4_int64 = {0x0, 0x0, 0x0, 0x0},
v2_int128 = {0x0, 0x0}}
// all are 1
ymm0
{v8_float = {0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff},
v4_double = {0x7fffffffffffffff, 0x7fffffffffffffff, 0x7fffffffffffffff, 0x7fffffffffffffff},
v32_int8 = {0xff <repeats 32 times>},
v16_int16 = {0xffff <repeats 16 times>},
v8_int32 = {0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff},
v4_int64 = {0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff},
v2_int128 = {0xffffffffffffffffffffffffffffffff, 0xffffffffffffffffffffffffffffffff}}
- 要将 0xA 设置为
ymm0
中的所有位置(高位和低位 128 位):
__asm__ __volatile__(
"movq $0xaaaaaaaaaaaaaaaa, %%rcx\n"
"vmovq %%rcx, %%xmm0\n"
"vpbroadcastq %%xmm0, %%ymm0\n": : :);
GDB 结果表明:
ymm0
{v8_float = {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0},
v4_double = {0x0, 0x0, 0x0, 0x0},
v32_int8 = {0xaa <repeats 32 times>},
v16_int16 = {0xaaaa <repeats 16 times>},
v8_int32 = {0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa},
v4_int64 = {0xaaaaaaaaaaaaaaaa, 0xaaaaaaaaaaaaaaaa, 0xaaaaaaaaaaaaaaaa, 0xaaaaaaaaaaaaaaaa},
v2_int128 = {0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa, 0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa}}
问题:
- GDB 结果(结构)是什么意思?例如,v8_float、v4_double、v32_int8等。
- 在第二种情况(0xA)中,为什么v8_float和v4_double总是0?<
- 如何将值(例如“a”)分配给 YMM 中的所有位置(包括高位和低位 128 位)?
最佳答案
首先,您的内联汇编已损坏:缺少一个 "%ymm0"
clobber 来告诉编译器您编写了该寄存器。您甚至使用 asm("": : :)
扩展 asm 语法来明确告诉编译器没有破坏者。或者更好,https://gcc.gnu.org/wiki/DontUseInlineAsm - 编写一个单独的函数,或使用内部函数并查看编译器生成的 asm。
v8_float
表示将 256 位解释为 8x float
的向量。即 Intel Intrinsics 中的 __m256
。
v32_int8
是 32x int8_t
的向量,分别打印每个字节。如果您想这样查看,可以使用 p/x $ymm0.v8_int32
。
(2) 整数0xa
是非常小的次正规 float 或 double 的位模式。尝试将其作为“十六进制表示”放在 https://www.h-schmidt.net/FloatConverter/IEEE754.html 上.
(3) 您已经将 0xa
广播到 32 字节 YMM 寄存器中的所有 64 个半字节,如您从 v2_int128 = {0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa, 0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa 中看到的那样aa}}
输出显示两半都是 0xaa
字节。
如果您确实想要_mm256_set1_epi8(0x0a)
(将其广播到每个字节),则应该编写0x0a0a0a0a
而不是0xaaaaaaaa
。 (无需使用 qword 立即数;vpbroadcastd
运行速度同样快,但 mov $0x0a0a0a0a, %eax
是一条更小、更快的指令。)
https://godbolt.org/z/z18nMT3fd显示 GCC 和 clang 编译一个返回 _mm256_set1_epi8(0x0a)
的函数(以及另一个广播函数 arg,而不是常量)的函数。 GCC11.3 进行常量传播并从 .rodata
加载 32 个字节。 GCC12.1 不明智地使用了 mov r64、imm64 和 vmovq
策略。
Clang 使用 8 字节内存源中的 vbroadcastsd
(与 vpbroadcastq
相同)。 4 字节广播负载同样高效。 (与花费额外 ALU uop 的字节或字不同:https://uops.info/ 和 https://agner.org/optimize/)
AVX-512 引入了 vpbroadcastb/w/d/q ymm0, eax
,它将 vmovd
与广播结合起来。但如果没有这个,如果数据来自整数寄存器,您通常需要 AVX2 vpbroadcastb/w/d/q ymm, xmm
。 (我在这里使用 Intel 语法,就像供应商手册一样;如果您愿意,可以像平常一样反转 AT&T 语法。)
据我所知,没有什么好技巧可以从全 1 动态生成 0xa (0b1010)。其他一些常量(例如 0x1 或 0x8000000)可以使用 2 条指令生成,从 vpcmpeqd same,same,same
开始(全 1)。 (参见What are the best instruction sequences to generate vector constants on the fly?)
关于assembly - 如何将AVX ymm寄存器中的所有值设置为相同(均为0/1/特定值)?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/72484239/