具有内在函数和汇编的嵌入式广播
Embedded broadcasts with intrinsics and assembly
在 the Intel Architecture Instruction Set Extensions Programming Reference 的第 2.5.3 "Broadcasts" 节中,我们了解到
AVX512(和骑士角)有
a bit-field to encode data broadcast for some load-op instructions, i.e. instructions that
load data from memory and perform some computational
or data movement operation.
例如,使用 Intel 汇编语法,我们可以在 rax
中存储的地址处广播标量,然后与 zmm2
中的 16 个浮点数相乘,并将结果写入 zmm1
,例如这个
vmulps zmm1, zmm2, [rax] {1to16}
但是,没有可以做到这一点的内在函数。因此,使用内部函数,编译器应该能够折叠
__m512 bb = _mm512_set1_ps(b);
__m512 ab = _mm512_mul_ps(a,bb);
单条指令
vmulps zmm1, zmm2, [rax] {1to16}
但我没有观察到 GCC 这样做。我找到了 GCC bug report about this。
我观察到与 FMA 和 GCC 类似的情况。例如GCC 4.9 不会崩溃 _mm256_add_ps(_mm256_mul_ps(areg0,breg0)
to a single fma instruction with -Ofast
。但是,GCC 5.1 现在确实将其压缩为单个 fma。至少有一些内在函数可以用 FMA 做到这一点,例如_mm256_fmadd_ps
。但是没有例如_mm512_mulbroad_ps(vector,scalar)
内在。
GCC 可能会在某个时候解决这个问题,但在那之前汇编是唯一的解决方案。
所以我的问题是如何在 GCC 中使用内联汇编来做到这一点?
我想我可能已经为上面的示例提出了 GCC 内联汇编的正确语法(但我不确定)。
"vmulps (%%rax)%{1to16}, %%zmm1, %%zmm2\n\t"
我真的很想找这样的功能
static inline __m512 mul_broad(__m512 a, float b) {
return a*b;
}
如果 b
在内存中指向 rax
它会产生
vmulps (%rax){1to16}, %zmm0, %zmm0
ret
如果 b
在 xmm1
中,它会产生
vbroadcastss %xmm1, %zmm1
vmulps %zmm1, %zmm0, %zmm0
ret
GCC 已经使用内部函数执行 vbroadcastss
-from-register 的情况,但是如果 b
在内存中,则从内存中将其编译为 vbroadcastss
。
__m512 mul_broad(__m512 a, float b) {
__m512 bb = _mm512_set1_ps(b);
__m512 ab = _mm512_mul_ps(a,bb);
return ab;
}
clang will use a broadcast memory operand 如果 b
在内存中。
正如 Peter Cordes 所指出的,GCC 不允许您为不同的约束选项指定不同的模板。因此,我的解决方案是让汇编器根据所选的操作数选择正确的指令。
我没有支持 ZMM 寄存器的 GCC 版本,因此以下示例使用 XMM 寄存器和一些不存在的指令来演示如何实现您正在寻找的内容。
typedef __attribute__((vector_size(16))) float v4sf;
v4sf
foo(v4sf a, float b) {
v4sf ret;
asm(".ifndef isxmm\n\t"
".altmacro\n\t"
".macro ifxmm operand, rnum\n\t"
".ifc \"\operand\",\"%%xmm\rnum\"\n\t"
".set isxmm, 1\n\t"
".endif\n\t"
".endm\n\t"
".endif\n\t"
".set isxmm, 0\n\t"
".set regnum, 0\n\t"
".rept 8\n\t"
"ifxmm <%2>, %%regnum\n\t"
".set regnum, regnum + 1\n\t"
".endr\n\t"
".if isxmm\n\t"
"alt-1 %1, %2, %0\n\t"
".else\n\t"
"alt-2 %1, %2, %0\n\t"
".endif\n\t"
: "=x,x" (ret)
: "x,x" (a), "x,m" (b));
return ret;
}
v4sf
bar(v4sf a, v4sf b) {
return foo(a, b[0]);
}
此示例应使用 gcc -m32 -msse -O3
进行编译,并且应生成两条类似于以下内容的汇编程序错误消息:
t103.c: Assembler messages:
t103.c:24: Error: no such instruction: `alt-2 %xmm0,4(%esp),%xmm0'
t103.c:22: Error: no such instruction: `alt-1 %xmm0,%xmm1,%xmm0'
这里的基本思想是汇编程序检查第二个操作数 (%2
) 是 XMM 寄存器还是其他东西,大概是内存位置。由于 GNU 汇编程序不支持对字符串进行很多操作,因此在 .rept
循环中一次将第二个操作数与每个可能的 XMM 寄存器进行比较。 isxmm
宏用于将 %xmm
和寄存器号粘贴在一起。
对于您的具体问题,您可能需要像这样重写它:
__m512
mul_broad(__m512 a, float b) {
__m512 ret;
__m512 dummy;
asm(".ifndef isxmm\n\t"
".altmacro\n\t"
".macro ifxmm operand, rnum\n\t"
".ifc \"\operand\",\"%%zmm\rnum\"\n\t"
".set isxmm, 1\n\t"
".endif\n\t"
".endm\n\t"
".endif\n\t"
".set isxmm, 0\n\t"
".set regnum, 0\n\t"
".rept 32\n\t"
"ifxmm <%[b]>, %%regnum\n\t"
".set regnum, regnum + 1\n\t"
".endr\n\t"
".if isxmm\n\t"
"vbroadcastss %x[b], %[b]\n\t"
"vmulps %[a], %[b], %[ret]\n\t"
".else\n\t"
"vmulps %[b] %{1to16%}, %[a], %[ret]\n\t"
"# dummy = %[dummy]\n\t"
".endif\n\t"
: [ret] "=x,x" (ret), [dummy] "=xm,x" (dummy)
: [a] "x,xm" (a), [b] "m,[dummy]" (b));
return ret;
}
在 the Intel Architecture Instruction Set Extensions Programming Reference 的第 2.5.3 "Broadcasts" 节中,我们了解到 AVX512(和骑士角)有
a bit-field to encode data broadcast for some load-op instructions, i.e. instructions that load data from memory and perform some computational or data movement operation.
例如,使用 Intel 汇编语法,我们可以在 rax
中存储的地址处广播标量,然后与 zmm2
中的 16 个浮点数相乘,并将结果写入 zmm1
,例如这个
vmulps zmm1, zmm2, [rax] {1to16}
但是,没有可以做到这一点的内在函数。因此,使用内部函数,编译器应该能够折叠
__m512 bb = _mm512_set1_ps(b);
__m512 ab = _mm512_mul_ps(a,bb);
单条指令
vmulps zmm1, zmm2, [rax] {1to16}
但我没有观察到 GCC 这样做。我找到了 GCC bug report about this。
我观察到与 FMA 和 GCC 类似的情况。例如GCC 4.9 不会崩溃 _mm256_add_ps(_mm256_mul_ps(areg0,breg0)
to a single fma instruction with -Ofast
。但是,GCC 5.1 现在确实将其压缩为单个 fma。至少有一些内在函数可以用 FMA 做到这一点,例如_mm256_fmadd_ps
。但是没有例如_mm512_mulbroad_ps(vector,scalar)
内在。
GCC 可能会在某个时候解决这个问题,但在那之前汇编是唯一的解决方案。
所以我的问题是如何在 GCC 中使用内联汇编来做到这一点?
我想我可能已经为上面的示例提出了 GCC 内联汇编的正确语法(但我不确定)。
"vmulps (%%rax)%{1to16}, %%zmm1, %%zmm2\n\t"
我真的很想找这样的功能
static inline __m512 mul_broad(__m512 a, float b) {
return a*b;
}
如果 b
在内存中指向 rax
它会产生
vmulps (%rax){1to16}, %zmm0, %zmm0
ret
如果 b
在 xmm1
中,它会产生
vbroadcastss %xmm1, %zmm1
vmulps %zmm1, %zmm0, %zmm0
ret
GCC 已经使用内部函数执行 vbroadcastss
-from-register 的情况,但是如果 b
在内存中,则从内存中将其编译为 vbroadcastss
。
__m512 mul_broad(__m512 a, float b) {
__m512 bb = _mm512_set1_ps(b);
__m512 ab = _mm512_mul_ps(a,bb);
return ab;
}
clang will use a broadcast memory operand 如果 b
在内存中。
正如 Peter Cordes 所指出的,GCC 不允许您为不同的约束选项指定不同的模板。因此,我的解决方案是让汇编器根据所选的操作数选择正确的指令。
我没有支持 ZMM 寄存器的 GCC 版本,因此以下示例使用 XMM 寄存器和一些不存在的指令来演示如何实现您正在寻找的内容。
typedef __attribute__((vector_size(16))) float v4sf;
v4sf
foo(v4sf a, float b) {
v4sf ret;
asm(".ifndef isxmm\n\t"
".altmacro\n\t"
".macro ifxmm operand, rnum\n\t"
".ifc \"\operand\",\"%%xmm\rnum\"\n\t"
".set isxmm, 1\n\t"
".endif\n\t"
".endm\n\t"
".endif\n\t"
".set isxmm, 0\n\t"
".set regnum, 0\n\t"
".rept 8\n\t"
"ifxmm <%2>, %%regnum\n\t"
".set regnum, regnum + 1\n\t"
".endr\n\t"
".if isxmm\n\t"
"alt-1 %1, %2, %0\n\t"
".else\n\t"
"alt-2 %1, %2, %0\n\t"
".endif\n\t"
: "=x,x" (ret)
: "x,x" (a), "x,m" (b));
return ret;
}
v4sf
bar(v4sf a, v4sf b) {
return foo(a, b[0]);
}
此示例应使用 gcc -m32 -msse -O3
进行编译,并且应生成两条类似于以下内容的汇编程序错误消息:
t103.c: Assembler messages:
t103.c:24: Error: no such instruction: `alt-2 %xmm0,4(%esp),%xmm0'
t103.c:22: Error: no such instruction: `alt-1 %xmm0,%xmm1,%xmm0'
这里的基本思想是汇编程序检查第二个操作数 (%2
) 是 XMM 寄存器还是其他东西,大概是内存位置。由于 GNU 汇编程序不支持对字符串进行很多操作,因此在 .rept
循环中一次将第二个操作数与每个可能的 XMM 寄存器进行比较。 isxmm
宏用于将 %xmm
和寄存器号粘贴在一起。
对于您的具体问题,您可能需要像这样重写它:
__m512
mul_broad(__m512 a, float b) {
__m512 ret;
__m512 dummy;
asm(".ifndef isxmm\n\t"
".altmacro\n\t"
".macro ifxmm operand, rnum\n\t"
".ifc \"\operand\",\"%%zmm\rnum\"\n\t"
".set isxmm, 1\n\t"
".endif\n\t"
".endm\n\t"
".endif\n\t"
".set isxmm, 0\n\t"
".set regnum, 0\n\t"
".rept 32\n\t"
"ifxmm <%[b]>, %%regnum\n\t"
".set regnum, regnum + 1\n\t"
".endr\n\t"
".if isxmm\n\t"
"vbroadcastss %x[b], %[b]\n\t"
"vmulps %[a], %[b], %[ret]\n\t"
".else\n\t"
"vmulps %[b] %{1to16%}, %[a], %[ret]\n\t"
"# dummy = %[dummy]\n\t"
".endif\n\t"
: [ret] "=x,x" (ret), [dummy] "=xm,x" (dummy)
: [a] "x,xm" (a), [b] "m,[dummy]" (b));
return ret;
}