使用gcc使用YMM指令添加数组

使用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)时,无论您在哪里写入

我想在gcc(AT&T语法)中运行以下代码(英特尔语法)

但是我不知道如何指定%ecx作为我的索引寄存器

你能帮帮我吗

编辑

我尝试过(%1,%%ecx):


我不认为这是可能的翻译成字面上的气体内联组装。在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
)使用寄存器约束时,您没有问题。请注意,这需要稍微修改代码

内联程序集的其他错误包括:

  • 数字文字以
    $
    开头
  • 指令应该有大小后缀,如32位的
    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