AVX512 掩码寄存器 (k1...k7) 的 GNU C 内联汇编输入约束?
GNU C inline asm input constraint for AVX512 mask registers (k1...k7)?
AVX512 为其算术命令引入了 opmask 功能。一个简单的例子:godbolt.org.
#include <immintrin.h>
__m512i add(__m512i a, __m512i b) {
__m512i sum;
asm(
"mov ebx, 0xAAAAAAAA; \n\t"
"kmovw k1, ebx; \n\t"
"vpaddd %[SUM] %{k1%}%{z%}, %[A], %[B]; # conditional add "
: [SUM] "=v"(sum)
: [A] "v" (a),
[B] "v" (b)
: "ebx", "k1" // clobbers
);
return sum;
}
-march=skylake-avx512 -masm=intel -O3
mov ebx,0xaaaaaaaa
kmovw k1,ebx
vpaddd zmm0{k1}{z},zmm0,zmm1
问题是必须指定 k1。
对于整数是否有类似 "r"
的输入约束,只是它选择 k
寄存器而不是通用寄存器?
虽然它没有记录,但查看 here 我们看到:
(define_register_constraint "Yk" "TARGET_AVX512F ? MASK_REGS :
NO_REGS" "@internal Any mask register that can be used as predicate,
i.e. k1-k7.")
正在编辑你的神栓:
asm(
"vpaddd %[SUM] %{%[k]}, %[A], %[B]"
: [SUM] "=v"(sum)
: [A] "v" (a), [B] "v" (b), [k] "Yk" (0xaaaaaaaa) );
似乎产生了正确的输出。
就是说,我通常会尝试 discourage 人们不要使用内联汇编(和未记录的功能)。你可以使用 _mm512_mask_add_epi32
吗?
__mmask16
实际上是 unsigned short
的类型定义(以及其他普通整数类型的其他掩码类型),所以我们只需要一个约束来将它传递到 k
寄存器中。
我们必须深入挖掘 gcc 源代码 config/i386/constraints.md
才能找到它:
任何掩码寄存器的约束是"k"
。 或者对 k1..k7
使用 "Yk"
(它可以用作谓词,不像 k0
)。 你会使用 "=k"
例如,操作数作为比较掩码的目标。
显然,您可以将 "=Yk"(tmp)
与 __mmask16 tmp
一起使用,让编译器为您分配寄存器,而不仅仅是在您决定使用的任何 "k"
寄存器上声明 clobber。
更喜欢像 _mm512_maskz_add_epi32
这样的内在函数
首先,https://gcc.gnu.org/wiki/DontUseInlineAsm if you can avoid it. Understanding asm is great, but use that to read compiler output and/or figure out what would be optimal, then write intrinsics that can compile the way you want. Performance tuning info like https://agner.org/optimize/ and https://uops.info/ list things by asm mnemonic, and they're shorter / easier to remember than intrinsics, but you can search by mnemonic to find intrinsics on https://software.intel.com/sites/landingpage/IntrinsicsGuide/
Intrinsics 还将让编译器将加载折叠到其他指令的内存源操作数中;使用 AVX512 甚至可以广播负载!您的内联汇编强制编译器使用单独的加载指令。 即使是 "vm"
输入也不会让编译器选择广播加载作为内存源,因为它不知道指令的广播元素宽度你正在使用它。
使用 _mm512_mask_add_epi32
或 _mm512_maskz_add_epi32
特别是如果您已经在使用 <immintrin.h>
.[=66 中的 __m512i
类型=]
此外,你的 asm 有一个错误:你使用的是 {k1}
合并屏蔽而不是 {k1}{z}
零屏蔽 ,但你使用了未初始化的 __m512i sum;
将仅输出 "=v"
约束作为合并目标!作为一个独立的函数,恰好合并到a
中,因为调用约定有ZMM0 = first input = return value register。但是当内联到其他函数时,你绝对不能假设 sum
会选择与 a
相同的寄存器。最好的办法是对 "+v"(a)
使用 read/write 操作数,并使用 is 作为目标和第一个源。
合并屏蔽仅对 "+v"
read/write 操作数有意义。(或者在您已经编写的包含多个指令的 asm 语句中输出一次,并想将另一个结果合并到其中。)
内在函数会阻止你犯这个错误;合并屏蔽版本有一个额外的合并目标输入。 (asm 目标操作数)。
使用“Yk”的示例
// works with -march=skylake-avx512 or -march=knl
// or just -mavx512f but don't do that.
// also needed: -masm=intel
#include <immintrin.h>
__m512i add_zmask(__m512i a, __m512i b) {
__m512i sum;
asm(
"vpaddd %[SUM] %{%[mask]%}%{z%}, %[A], %[B]; # conditional add "
: [SUM] "=v"(sum)
: [A] "v" (a),
[B] "v" (b),
[mask] "Yk" ((__mmask16)0xAAAA)
// no clobbers needed, unlike your question which I fixed with an edit
);
return sum;
}
请注意,所有 {
和 }
都使用 %
(https://gcc.gnu.org/onlinedocs/gcc/Extended-Asm.html#Special-format-strings) 进行了转义,因此它们不会被解析为方言替代词 {AT&T | Intel-syntax}
.
早在 4.9 就可以使用 gcc 进行编译,但实际上并没有这样做,因为它不理解 -march=skylake-avx512
,甚至没有针对 Skylake 或 KNL 的调整设置。使用更新的 GCC,了解您的 CPU 以获得最佳结果。
# gcc8.3 -O3 -march=skylake-avx512 or -march=knl (and -masm=intel)
add(long long __vector, long long __vector):
mov eax, -21846
kmovw k1, eax # compiler-generated
# inline asm starts
vpaddd zmm0 {k1}{z}, zmm0, zmm1; # conditional add
# inline asm ends
ret
-mavx512bw
(由 -march=skylake-avx512
暗示但不是 knl
)是 "Yk"
在 int
。如果您使用 -march=knl
进行编译,整数文字需要转换为 __mmask16
或 __mask8
,因为 unsigned int = __mask32
不适用于掩码。
[mask] "Yk" (0xAAAA)
需要 AVX512BW,即使常量确实适合 16 位,只是因为裸整数文字总是具有类型 int
。 (vpaddd
zmm 每个向量有 16 个元素,所以我将你的常量缩短为 16 位。)使用 AVX512BW,你可以传递更宽的常量或为窄的常量省略转换。
- gcc6 及更高版本支持
-march=skylake-avx512
。使用它来设置调整以及启用所有内容。最好是 gcc8 或至少是 gcc7。如果您在内联 asm 之外使用新的 ISA 扩展(如 AVX512),较新的编译器会生成不那么笨重的代码。
- gcc5 支持
-mavx512f -mavx512bw
但不知道 Skylake。
- gcc4.9 不支持
-mavx512bw
.
不幸的是,"Yk"
尚未记录在 https://gcc.gnu.org/onlinedocs/gcc/Machine-Constraints.html.
中
感谢 Ross 在
上的回答,我知道在哪里可以查看 GCC 源代码
AVX512 为其算术命令引入了 opmask 功能。一个简单的例子:godbolt.org.
#include <immintrin.h>
__m512i add(__m512i a, __m512i b) {
__m512i sum;
asm(
"mov ebx, 0xAAAAAAAA; \n\t"
"kmovw k1, ebx; \n\t"
"vpaddd %[SUM] %{k1%}%{z%}, %[A], %[B]; # conditional add "
: [SUM] "=v"(sum)
: [A] "v" (a),
[B] "v" (b)
: "ebx", "k1" // clobbers
);
return sum;
}
-march=skylake-avx512 -masm=intel -O3
mov ebx,0xaaaaaaaa
kmovw k1,ebx
vpaddd zmm0{k1}{z},zmm0,zmm1
问题是必须指定 k1。
对于整数是否有类似 "r"
的输入约束,只是它选择 k
寄存器而不是通用寄存器?
虽然它没有记录,但查看 here 我们看到:
(define_register_constraint "Yk" "TARGET_AVX512F ? MASK_REGS : NO_REGS" "@internal Any mask register that can be used as predicate, i.e. k1-k7.")
正在编辑你的神栓:
asm(
"vpaddd %[SUM] %{%[k]}, %[A], %[B]"
: [SUM] "=v"(sum)
: [A] "v" (a), [B] "v" (b), [k] "Yk" (0xaaaaaaaa) );
似乎产生了正确的输出。
就是说,我通常会尝试 discourage 人们不要使用内联汇编(和未记录的功能)。你可以使用 _mm512_mask_add_epi32
吗?
__mmask16
实际上是 unsigned short
的类型定义(以及其他普通整数类型的其他掩码类型),所以我们只需要一个约束来将它传递到 k
寄存器中。
我们必须深入挖掘 gcc 源代码 config/i386/constraints.md
才能找到它:
任何掩码寄存器的约束是"k"
。 或者对 k1..k7
使用 "Yk"
(它可以用作谓词,不像 k0
)。 你会使用 "=k"
例如,操作数作为比较掩码的目标。
显然,您可以将 "=Yk"(tmp)
与 __mmask16 tmp
一起使用,让编译器为您分配寄存器,而不仅仅是在您决定使用的任何 "k"
寄存器上声明 clobber。
更喜欢像 _mm512_maskz_add_epi32
这样的内在函数
首先,https://gcc.gnu.org/wiki/DontUseInlineAsm if you can avoid it. Understanding asm is great, but use that to read compiler output and/or figure out what would be optimal, then write intrinsics that can compile the way you want. Performance tuning info like https://agner.org/optimize/ and https://uops.info/ list things by asm mnemonic, and they're shorter / easier to remember than intrinsics, but you can search by mnemonic to find intrinsics on https://software.intel.com/sites/landingpage/IntrinsicsGuide/
Intrinsics 还将让编译器将加载折叠到其他指令的内存源操作数中;使用 AVX512 甚至可以广播负载!您的内联汇编强制编译器使用单独的加载指令。 即使是 "vm"
输入也不会让编译器选择广播加载作为内存源,因为它不知道指令的广播元素宽度你正在使用它。
使用 _mm512_mask_add_epi32
或 _mm512_maskz_add_epi32
特别是如果您已经在使用 <immintrin.h>
.[=66 中的 __m512i
类型=]
此外,你的 asm 有一个错误:你使用的是 {k1}
合并屏蔽而不是 {k1}{z}
零屏蔽 ,但你使用了未初始化的 __m512i sum;
将仅输出 "=v"
约束作为合并目标!作为一个独立的函数,恰好合并到a
中,因为调用约定有ZMM0 = first input = return value register。但是当内联到其他函数时,你绝对不能假设 sum
会选择与 a
相同的寄存器。最好的办法是对 "+v"(a)
使用 read/write 操作数,并使用 is 作为目标和第一个源。
合并屏蔽仅对 "+v"
read/write 操作数有意义。(或者在您已经编写的包含多个指令的 asm 语句中输出一次,并想将另一个结果合并到其中。)
内在函数会阻止你犯这个错误;合并屏蔽版本有一个额外的合并目标输入。 (asm 目标操作数)。
使用“Yk”的示例
// works with -march=skylake-avx512 or -march=knl
// or just -mavx512f but don't do that.
// also needed: -masm=intel
#include <immintrin.h>
__m512i add_zmask(__m512i a, __m512i b) {
__m512i sum;
asm(
"vpaddd %[SUM] %{%[mask]%}%{z%}, %[A], %[B]; # conditional add "
: [SUM] "=v"(sum)
: [A] "v" (a),
[B] "v" (b),
[mask] "Yk" ((__mmask16)0xAAAA)
// no clobbers needed, unlike your question which I fixed with an edit
);
return sum;
}
请注意,所有 {
和 }
都使用 %
(https://gcc.gnu.org/onlinedocs/gcc/Extended-Asm.html#Special-format-strings) 进行了转义,因此它们不会被解析为方言替代词 {AT&T | Intel-syntax}
.
早在 4.9 就可以使用 gcc 进行编译,但实际上并没有这样做,因为它不理解 -march=skylake-avx512
,甚至没有针对 Skylake 或 KNL 的调整设置。使用更新的 GCC,了解您的 CPU 以获得最佳结果。
# gcc8.3 -O3 -march=skylake-avx512 or -march=knl (and -masm=intel)
add(long long __vector, long long __vector):
mov eax, -21846
kmovw k1, eax # compiler-generated
# inline asm starts
vpaddd zmm0 {k1}{z}, zmm0, zmm1; # conditional add
# inline asm ends
ret
-mavx512bw
(由 -march=skylake-avx512
暗示但不是 knl
)是 "Yk"
在 int
。如果您使用 -march=knl
进行编译,整数文字需要转换为 __mmask16
或 __mask8
,因为 unsigned int = __mask32
不适用于掩码。
[mask] "Yk" (0xAAAA)
需要 AVX512BW,即使常量确实适合 16 位,只是因为裸整数文字总是具有类型 int
。 (vpaddd
zmm 每个向量有 16 个元素,所以我将你的常量缩短为 16 位。)使用 AVX512BW,你可以传递更宽的常量或为窄的常量省略转换。
- gcc6 及更高版本支持
-march=skylake-avx512
。使用它来设置调整以及启用所有内容。最好是 gcc8 或至少是 gcc7。如果您在内联 asm 之外使用新的 ISA 扩展(如 AVX512),较新的编译器会生成不那么笨重的代码。 - gcc5 支持
-mavx512f -mavx512bw
但不知道 Skylake。 - gcc4.9 不支持
-mavx512bw
.
不幸的是,
"Yk"
尚未记录在 https://gcc.gnu.org/onlinedocs/gcc/Machine-Constraints.html.
感谢 Ross 在