使用gcc使用YMM指令添加数组
我想在gcc(AT&T语法)中运行以下代码(英特尔语法) 但是我不知道如何指定%ecx作为我的索引寄存器 你能帮帮我吗 编辑 我尝试过(%1,%%ecx):使用gcc使用YMM指令添加数组,gcc,assembly,x86,g++,avx,Gcc,Assembly,X86,G++,Avx,我想在gcc(AT&T语法)中运行以下代码(英特尔语法) 但是我不知道如何指定%ecx作为我的索引寄存器 你能帮帮我吗 编辑 我尝试过(%1,%%ecx): 我不认为这是可能的翻译成字面上的气体内联组装。在AT&T语法中,语法为: 位移(基址寄存器、偏移寄存器、标量乘法器) 这将产生类似于: movl -4(%ebp, %ecx, 4), %eax 或者在您的情况下: vmovaps -16(%rsp, %ecx, 0), %ymm0 问题是,当您使用内存约束(m)时,无论您在哪里写入
我不认为这是可能的翻译成字面上的气体内联组装。在AT&T语法中,语法为: 位移(基址寄存器、偏移寄存器、标量乘法器) 这将产生类似于:
movl -4(%ebp, %ecx, 4), %eax
或者在您的情况下:
vmovaps -16(%rsp, %ecx, 0), %ymm0
问题是,当您使用内存约束(m
)时,无论您在哪里写入%n
(其中n
是输入/输出的编号),内联汇编程序都会发出以下信息:
无法将上述内容转换为您实际需要的形式。你可以写:
(%1, %%rcx)
但这将产生:
(-16(%rsp),%rcx)
这显然是错误的。由于%n
将整个-16(%rsp)
作为一个块发送,因此无法将偏移量寄存器放在它所属的括号内
当然,这并不是一个真正的问题,因为您编写内联汇编是为了提高速度,而从内存加载并没有什么快速性。您应该将输入放在寄存器中,并且当您对输入/输出(r
)使用寄存器约束时,您没有问题。请注意,这需要稍微修改代码
内联程序集的其他错误包括:
$
开头l
和64位的q
a
写入时,您正在重击内存,因此您应该有一个内存
重击器nop
指令完全没有意义。他们甚至没有对准分支目标\n
)之外,每一行都应该以制表符(\t
)结尾,以便在检查反汇编时正确对齐void addArray(float *a, float *b, float *c) {
__asm__ __volatile__ (
"xorl %%ecx, %%ecx \n\t" // Loop counter set to 0
"loop: \n\t"
"vmovaps (%1,%%rcx), %%ymm0 \n\t" // Load 8 elements from b
"vaddps (%2,%%rcx), %%ymm0, %%ymm0 \n\t" // Add 8 elements from c
"vmovaps %%ymm0, (%0,%%rcx) \n\t" // Store result in a
"addl $0x20, %%ecx \n\t" // 8 elemtns * 4 bytes = 32 (0x20)
"cmpl $0x200, %%ecx \n\t" // 128 elements * 4 bytes = 512 (0x200)
"jb loop" // Loop"
: // Outputs
: "r" (a), "r" (b), "r" (c) // Inputs
: "%ecx", "%ymm0", "memory" // Modifies ECX, YMM0, and memory
);
}
这会导致编译器发出以下命令:
addArray(float*, float*, float*):
xorl %ecx, %ecx
loop:
vmovaps (%rsi,%rcx), %ymm0 # b
vaddps (%rdx,%rcx), %ymm0, %ymm0 # c
vmovaps %ymm0, (%rdi,%rcx) # a
addl $0x20, %ecx
cmpl $0x200, %ecx
jb loop
vzeroupper
retq
addArray(float*, float*, float*):
xor eax, eax
.L2:
vmovss xmm0, DWORD PTR [rsi+rax]
vaddss xmm0, xmm0, DWORD PTR [rdx+rax]
vmovss DWORD PTR [rdi+rax], xmm0
add rax, 4
cmp rax, 512
jne .L2
rep ret
或者,使用更熟悉的英特尔语法:
addArray(float*, float*, float*):
xor ecx, ecx
loop:
vmovaps ymm0, YMMWORD PTR [rsi + rcx]
vaddps ymm0, ymm0, YMMWORD PTR [rdx + rcx]
vmovaps YMMWORD PTR [rdi + rcx], ymm0
add ecx, 32
cmp ecx, 512
jb loop
vzeroupper
ret
在System V 64位调用约定中,前三个参数在rdi
、rsi
和rdx
寄存器中传递,因此代码不需要将参数移动到它们已经存在的寄存器中
但您没有充分使用输入/输出约束。您不需要使用rcx
作为计数器。也不需要使用ymm0
作为暂存寄存器。如果让编译器选择要使用的空闲寄存器,将使代码更高效。您也不需要提供明确的打击者列表:
#include <stdint.h>
#include <x86intrin.h>
void addArray(float *a, float *b, float *c) {
uint64_t temp = 0;
__m256 ymm;
__asm__ __volatile__(
"loop: \n\t"
"vmovaps (%3,%0), %1 \n\t" // Load 8 elements from b
"vaddps (%4,%0), %1, %1 \n\t" // Add 8 elements from c
"vmovaps %1, (%2,%0) \n\t" // Store result in a
"addl $0x20, %0 \n\t" // 8 elemtns * 4 bytes = 32 (0x20)
"cmpl $0x200, %0 \n\t" // 128 elements * 4 bytes = 512 (0x200)
"jb loop" // Loop
: "+r" (temp), "=x" (ymm)
: "r" (a), "r" (b), "r" (c)
: "memory"
);
}
嗯,那看起来很熟悉,不是吗?公平地说,它不像代码那样矢量化。您可以通过使用
-O3
或-ftree vectorize
来实现这一点,但您也可以实现,因此我需要一个基准测试来说服我,它实际上更快,值得代码大小的爆炸。但这大部分是为了处理输入不对齐的情况-。请注意,它完全展开了循环,并对添加进行了矢量化。这不需要使用汇编,gcc也有矢量内置项并支持内部函数。是的,我知道。我只需要学习如何使用ASM。使用r
约束。另外,别忘了添加一个内存
clobber。为了获得最佳效果,不要绑定ecx
和ymm0
,也不要将调零留给编译器。更不用说对齐循环目标了。当然,正如我已经说过的,您不应该为此使用汇编。这里的练习是使用gcc在asm中编写代码。我知道我可以在普通c中使用内部函数实现同样的算法。我知道如何用Intel/Visual Studio语法编写代码。(原代码)。我在这里试图实现的是使用gcc。考虑到这一点,我想在汇编程序中进行循环。我不知道如何使用ecx进行索引,即使使用r约束…(%1,%%ecx)
。另请注意,您可以将gcc切换到英特尔语法模式。您不是在写入a
而是从a
指向的任何位置开始写入内存。因此,您不需要a
作为输出,但确实需要内存
clobber。此外,即使您可以得到语法正确的-16(%rsp,%ecx,0)
来发出,这仍然不起作用,因为它将缺少一个间接级别。最后,显示的C代码虽然熟悉,但不幸的是没有矢量化,它使用的是标量。谢谢,修好了。您可以使用-O3
或-ftree-vectorize
将其矢量化,但您可以获得更多的代码。我需要对它进行基准测试,以验证它实际上更快。可能是因为我没有指出它是对齐的。“整个练习都是在浪费时间”——事实并非如此。通过这个练习,我学到了很多东西。谢谢你们!你是最好的。只是为了好玩,没有什么科学性,我做了一个小基准测试,运行了100万次。我不得不在循环中引入一个开销虚拟代码,以避免编译器从代码中分支出整个无用的循环。在没有任何优化(-O0)的情况下编译,我的内联代码大约需要120ms,而C(标量)代码大约需要449ms(如预期的那样)。使用-O3-mavx2编译,我的代码大约需要50毫秒,而C(矢量化)代码大约需要58毫秒。Gcc做得很好。它生成的代码在大小上是爆炸式的,但它非常高效。
(%1, %%rcx)
(-16(%rsp),%rcx)
void addArray(float *a, float *b, float *c) {
__asm__ __volatile__ (
"xorl %%ecx, %%ecx \n\t" // Loop counter set to 0
"loop: \n\t"
"vmovaps (%1,%%rcx), %%ymm0 \n\t" // Load 8 elements from b
"vaddps (%2,%%rcx), %%ymm0, %%ymm0 \n\t" // Add 8 elements from c
"vmovaps %%ymm0, (%0,%%rcx) \n\t" // Store result in a
"addl $0x20, %%ecx \n\t" // 8 elemtns * 4 bytes = 32 (0x20)
"cmpl $0x200, %%ecx \n\t" // 128 elements * 4 bytes = 512 (0x200)
"jb loop" // Loop"
: // Outputs
: "r" (a), "r" (b), "r" (c) // Inputs
: "%ecx", "%ymm0", "memory" // Modifies ECX, YMM0, and memory
);
}
addArray(float*, float*, float*):
xorl %ecx, %ecx
loop:
vmovaps (%rsi,%rcx), %ymm0 # b
vaddps (%rdx,%rcx), %ymm0, %ymm0 # c
vmovaps %ymm0, (%rdi,%rcx) # a
addl $0x20, %ecx
cmpl $0x200, %ecx
jb loop
vzeroupper
retq
addArray(float*, float*, float*):
xor ecx, ecx
loop:
vmovaps ymm0, YMMWORD PTR [rsi + rcx]
vaddps ymm0, ymm0, YMMWORD PTR [rdx + rcx]
vmovaps YMMWORD PTR [rdi + rcx], ymm0
add ecx, 32
cmp ecx, 512
jb loop
vzeroupper
ret
#include <stdint.h>
#include <x86intrin.h>
void addArray(float *a, float *b, float *c) {
uint64_t temp = 0;
__m256 ymm;
__asm__ __volatile__(
"loop: \n\t"
"vmovaps (%3,%0), %1 \n\t" // Load 8 elements from b
"vaddps (%4,%0), %1, %1 \n\t" // Add 8 elements from c
"vmovaps %1, (%2,%0) \n\t" // Store result in a
"addl $0x20, %0 \n\t" // 8 elemtns * 4 bytes = 32 (0x20)
"cmpl $0x200, %0 \n\t" // 128 elements * 4 bytes = 512 (0x200)
"jb loop" // Loop
: "+r" (temp), "=x" (ymm)
: "r" (a), "r" (b), "r" (c)
: "memory"
);
}
void addArray(float *a, float *b, float *c) {
for (int i = 0; i < 128; i++) a[i] = b[i] + c[i];
}
addArray(float*, float*, float*):
xor eax, eax
.L2:
vmovss xmm0, DWORD PTR [rsi+rax]
vaddss xmm0, xmm0, DWORD PTR [rdx+rax]
vmovss DWORD PTR [rdi+rax], xmm0
add rax, 4
cmp rax, 512
jne .L2
rep ret