当前位置: 首页 > 知识库问答 >
问题:

VXORPS在AMD捷豹/推土机/Zen上的归零使用xmm寄存器比YMM快吗?

陈开宇
2023-03-14

Agner Fog的指令表没有列出这种特殊情况,他的microarch指南也没有提到UOP的数量。

这可能意味着vxorps xmm0,xmm0,xmm0是实现_mm256_setzero_ps()的更好方法。

对于AVX512,_mm512_setzero_ps()还通过在可能的情况下仅使用vex编码的零化习惯用法而不是EVEX来保存字节。(即对于zmm0-15.vxorps xmm31、xmm31、xmm31仍然需要EVEX)。GCC/CLANG目前使用所需寄存器宽度的XOR零化习惯用法,而不是总是使用AVX-128。

(256B向量指令在第一个256B指令之后的第一个~56K循环中速度较慢。参见Agner Fog的microarch pdf中的Skylake部分)。如果调用返回_mm256_setzero_psnoinline函数不是使执行单元升温的可靠方法,那么这可能是可以的。(在没有AVX2的情况下仍然可以工作并避免任何加载(可能会丢失缓存)的是__m128 onebits=_mm_castsi128_ps(_mm_set1_epi8(0xff));

return_mm256_insertf128_ps(_mm256_castps128_ps(_mm256_castps256(onebits),onebits),它应该编译为pcmpeqd xmm0,xmm0,好在关键循环之前。如果你想要一些可以内联的东西,你可能需要内联-ASM。)

我没有AMD硬件所以我不能测试这个。

如果有人有AMD硬件,但不知道如何测试,使用性能计数器来计算周期(最好是m-ops或uops或AMD称之为它们的任何东西)。

这是我用来测试短序列的NASM/YASM源代码:

section .text
global _start
_start:

    mov     ecx, 250000000

align 32  ; shouldn't matter, but just in case
.loop:

    dec     ecx  ; prevent macro-fusion by separating this from jnz, to avoid differences on CPUs that can't macro-fuse

%rep 6
    ;    vxorps  xmm1, xmm1, xmm1
    vxorps  ymm1, ymm1, ymm1
%endrep

    jnz .loop

    xor edi,edi
    mov eax,231    ; exit_group(0) on x86-64 Linux
    syscall

如果您不在Linux上,可以用ret替换循环(退出系统调用)之后的内容,并从Cmain()函数调用该函数。

使用nasm-felf64 vxor-zero.asm&&ld-o vxor-zero vxor-zero.o进行组装,生成一个静态二进制文件。(或者使用asm-link脚本,这是我在一个关于使用/不使用libc组装静态/动态二进制文件的问答中发布的)。

$ alias disas='objdump -drwC -Mintel'
$ b=vxor-zero;  asm-link "$b.asm" && disas "$b" && ocperf.py stat -etask-clock,cycles,instructions,branches,uops_issued.any,uops_retired.retire_slots,uops_executed.thread -r4 "./$b"
+ yasm -felf64 -Worphan-labels -gdwarf2 vxor-zero.asm
+ ld -o vxor-zero vxor-zero.o

vxor-zero:     file format elf64-x86-64


Disassembly of section .text:

0000000000400080 <_start>:
  400080:       b9 80 b2 e6 0e          mov    ecx,0xee6b280
  400085:       66 66 66 66 66 66 2e 0f 1f 84 00 00 00 00 00    data16 data16 data16 data16 data16 nop WORD PTR cs:[rax+rax*1+0x0]
  400094:       66 66 66 2e 0f 1f 84 00 00 00 00 00     data16 data16 nop WORD PTR cs:[rax+rax*1+0x0]

00000000004000a0 <_start.loop>:
  4000a0:       ff c9                   dec    ecx
  4000a2:       c5 f4 57 c9             vxorps ymm1,ymm1,ymm1
  4000a6:       c5 f4 57 c9             vxorps ymm1,ymm1,ymm1
  4000aa:       c5 f4 57 c9             vxorps ymm1,ymm1,ymm1
  4000ae:       c5 f4 57 c9             vxorps ymm1,ymm1,ymm1
  4000b2:       c5 f4 57 c9             vxorps ymm1,ymm1,ymm1
  4000b6:       c5 f4 57 c9             vxorps ymm1,ymm1,ymm1
  4000ba:       75 e4                   jne    4000a0 <_start.loop>
  4000bc:       31 ff                   xor    edi,edi
  4000be:       b8 e7 00 00 00          mov    eax,0xe7
  4000c3:       0f 05                   syscall

(ocperf.py is a wrapper with symbolic names for CPU-specific events.  It prints the perf command it actually ran):

perf stat -etask-clock,cycles,instructions,branches,cpu/event=0xe,umask=0x1,name=uops_issued_any/,cpu/event=0xc2,umask=0x2,name=uops_retired_retire_slots/,cpu/event=0xb1,umask=0x1,name=uops_executed_thread/ -r4 ./vxor-zero

 Performance counter stats for './vxor-zero' (4 runs):

        128.379226      task-clock:u (msec)       #    0.999 CPUs utilized            ( +-  0.07% )
       500,072,741      cycles:u                  #    3.895 GHz                      ( +-  0.01% )
     2,000,000,046      instructions:u            #    4.00  insn per cycle           ( +-  0.00% )
       250,000,040      branches:u                # 1947.356 M/sec                    ( +-  0.00% )
     2,000,012,004      uops_issued_any:u         # 15578.938 M/sec                   ( +-  0.00% )
     2,000,008,576      uops_retired_retire_slots:u # 15578.911 M/sec                   ( +-  0.00% )
       500,009,692      uops_executed_thread:u    # 3894.787 M/sec                    ( +-  0.00% )

       0.128516502 seconds time elapsed                                          ( +-  0.09% )

uops_executed_thread是未使用的域UOP(执行端口)。异或归零不需要任何英特尔CPU,所以它只是实际执行的dec和分支UOP。(如果我们将操作数更改为vxorps,这样不仅仅是将寄存器归零,例如vxorps ymm2,ymm1,ymm0将输出写到下一个寄存器不读取的寄存器中,执行的uops将匹配融合域uop计数。我们会看到吞吐量限制是每个时钟3个vxorps。)

500M时钟周期发出2000M融合域uops是每个时钟发出4.0 uops:实现了理论上的最大前端吞吐量。6*250是1500,因此这些计数与Skylake解码vxorps ymm,ymm,ymm到1融合域UOP相匹配。

在循环中有不同数量的UOP,情况就不那么好了。例如,一个5 uop循环只在3.75 uops每时钟发出。我有意选择了8个UOP(当vxorps解码为单个UOP时)。


共有1个答案

郭俊拔
2023-03-14

对一个ymm寄存器本身进行异或会在AMD Ryzen上产生两个微操作,而对一个xmm寄存器本身进行异或只会产生一个微操作。因此,复制ymm寄存器的最佳方法是将对应的xmm寄存器与其自身进行异或,并依赖于隐式零扩展。

今天唯一支持AVX512的处理器是骑士登陆。它使用单个微操作对zmm寄存器进行异或。通过一分为二来处理向量大小的新扩展是非常常见的。这发生在从64位到128位的转换和从128位到256位的转换中。未来的一些处理器(来自AMD、Intel或任何其他供应商)很可能会将512位向量拆分为两个256位向量,甚至四个128位向量。因此,使zmm寄存器归零的最佳方法是将128位寄存器与其自身进行异或,并依赖于零扩展。你是对的,128位VEX编码的指令要短一两个字节。

大多数处理器识别寄存器与自身的异或独立于寄存器的前一个值。

 类似资料:
  • 因此,每个测试都有3+1=4个周期的延迟。 其中一些可以通过在、等之间交替并行运行。 但它仍然相当慢。 有没有更快的方法来实现这一点? 我需要在一行中测试8个XMM/YMM寄存器。一字节位图中每个寄存器1位。

  • 考虑: 为什么,我怎么才能让它起作用? 我的CPU是i5-10210u(支持AVX-256)。在X64版本/调试中运行。

  • 在x86-64中,如果某些通用寄存器比其他寄存器更受欢迎,某些指令会执行得更快吗? 例如,会比执行得更快吗?我可以想象后者需要一个REX前缀,这会使指令获取速度变慢? 使用代替怎么样?或呢?其他操作?更小的寄存器,如vs?vs? AMD vs Intel?更新的处理器?较旧的处理器?指令的组合? 澄清:某些通用登记册是否应该优先于其他登记册,它们是哪些?

  • Windows上是否有任何方法可以解决XMM寄存器保留在函数调用中的要求?(除了将其全部写入汇编中) 不幸的是,我有许多AVX2内在函数因此而臃肿。 例如,这将被编译器(MSVC)放置在函数的顶部: 00007FF9D0EBC602 vmovaps xmmword ptr[rsp 1490h]、xmm6 00007FF9 D0EBC60B vmovaps XMMWORDPTR[rsp 1480h]

  • 问题内容: 我正在从事Spring Boot项目。我只有注释配置。我想包括推土机以将实体转换为DTO,并将DTO转换为实体。我在推土机网站上看到了,他们解释说我必须在spring xml配置文件中添加以下配置。由于我没有xml文件,而是注释配置Java类,因此我不知道如何将其转换为Java Configuration类。 如果有人可以给我一个例子,它将非常有用。谢谢 问题答案: 我认为这样的事情应

  • 在Dozer的帮助下,我想将映射到DTO类 但是这两种设置