问题 使用xmm寄存器而不是ymm时,vxorps是否会更快地使用AMD Jaguar / Bulldozer / Zen?


AMD CPU通过解码为两个128b操作来处理256b AVX指令。例如 vaddps ymm0, ymm1,ymm1 AMD的Steamroller解码为2个宏操作,吞吐量的一半 vaddps xmm0, xmm1,xmm1

异或归零是一种特殊情况(没有输入依赖,并且 在Jaguar上至少避免使用物理寄存器文件条目,并且使得来自该寄存器的movdqa能够在发出/重命名时被消除,就像Bulldozer一直在为非零的regs做的那样。 但它是否足够早被发现 vxorps ymm0,ymm0,ymm0 仍然只解码为1个宏操作,具有相同的性能 vxorps xmm0,xmm0,xmm0? (不像 vxorps ymm3, ymm2,ymm1

或者,在已经解码为两个uops之后,独立检测是否会发生?此外,AMD CPU上的向量xor-zeroing是否仍然使用执行端口?在Intel-CPU上,Nehalem需要一个端口,但Sandybridge系列在发布/重命名阶段处理它。

Agner Fog的指令表没有列出这个特例,他的微指南没有提到uop的数量。


这可能意味着 vxorps xmm0,xmm0,xmm0 是一种更好的实施方式 _mm256_setzero_ps()

对于AVX512, _mm512_setzero_ps() 如果可能的话,也只使用VEX编码的归零惯用语而不是EVEX来保存字节。 (即zmm0-15。 vxorps xmm31,xmm31,xmm31 仍然需要EVEX)。 gcc / clang目前使用他们想要的任何寄存器宽度的xor-zeroing习语,而不是总是使用AVX-128。

报道为铿锵 错误32862 和gcc bug 80636。 MSVC已经使用了 xmm。尚未向ICC报告,ICC也使用zmm regs进行AVX512归零。 (虽然英特尔可能不会改变,因为目前任何英特尔CPU都没有任何优势,只有AMD。如果他们发布的低功耗CPU将矢量分成两半,他们可能。他们目前的低功耗设计(Silvermont)没有t支持AVX,只支持SSE4。)


我知道使用AVX-128指令清零256b寄存器唯一可能的缺点是它不会触发Intel CPU上256b执行单元的预热。可能会破坏试图加热它们的C或C ++黑客攻击。

(在第一个256b指令之后的第一个~56k周期内,256b向量指令较慢。请参阅Agner Fog微格式pdf中的Skylake部分)。如果打电话给你可能会好的 noinline 返回的功能 _mm256_setzero_ps 对于预热执行单元不是一种可靠的方法。 (一个仍然可以在没有AVX2的情况下工作,并且避免任何负载(可以缓存未命中)) __m128 onebits = _mm_castsi128_ps(_mm_set1_epi8(0xff));
return _mm256_insertf128_ps(_mm256_castps128_ps256(onebits), onebits) 应编译为 pcmpeqd xmm0,xmm0,xmm0 / vinsertf128 ymm0,xmm0,1。对于一些在紧急循环之前将执行单元预热(或保持温暖)的事情,这仍然是微不足道的。如果你想要内联的东西,你可能需要inline-asm。)


我没有AMD硬件所以我无法测试这个。

如果有人有AMD硬件但不知道如何测试,请使用perf计数器来计算周期(最好是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,并从C调用该函数 main() 功能。

与...组装 nasm -felf64 vxor-zero.asm && ld -o vxor-zero vxor-zero.o 制作一个静态二进制文件(或使用 asm-link 脚本我发布了关于使用/不使用libc组装静态/动态二进制文件的问答)。

i7-6700k(Intel Skylake)的输出示例,频率为3.9GHz。 (IDK为什么我的机器在闲置几分钟后才升至3.9GHz。启动后立即升级到4.2或4.4GHz正常工作)。由于我正在使用性能计数器,因此机器运行的时钟速度实际上并不重要。不涉及加载/存储或代码缓存未命中,因此无论它们有多长,所有内核的核心时钟周期数都是恒定的。

$ 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% )

+ - 0.02%的东西是因为我跑了 perf stat -r4,所以它运行我的二进制4次。

uops_issued_any 和 uops_retired_retire_slots 是融合域(Skylake和Bulldozer系列的每个时钟的前端吞吐量限制为4)。计数几乎相同,因为没有分支错误预测(导致推测性发布的uop被丢弃而不是退休)。

uops_executed_thread 是unfused-domain uops(执行端口)。 xor-zeroing在Intel CPU上不需要任何,所以它只是实际执行的dec和branch uops。 (如果我们将操作数更改为vxorps,那么它不仅仅是将寄存器归零,例如 vxorps ymm2, ymm1,ymm0 要将输出写入下一个未读取的寄存器,执行的uops将与融合域uop计数匹配。我们会看到吞吐量限制是每个时钟三个vxorps。)

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

在循环中使用不同数量的uops,事情并不是那么好。例如一个5 uop循环,每个时钟仅发出3.75微秒。我故意选择它为8 uops(当vxorps解码为单uop时)。

Zen的问题宽度是每循环6个uop,因此在不同的展开量下它可能会做得更好。 (看到 这个问答 有关更多关于uop计数不是问题宽度倍数的短循环的信息,请参阅Intel SnB-family uarches)。


11550
2018-05-01 01:53


起源



答案:


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

今天唯一支持AVX512的处理器是Knights Landing。它使用单个微操作来对xmm寄存器进行xor。通过将它分成两部分来处理矢量大小的新扩展是很常见的。这发生在从64位到128位的转换以及从128位到256位的转换。未来某些处理器(来自AMD或Intel或任何其他供应商)很可能会将512位向量分成两个256位向量甚至四个128位向量。因此,将zmm寄存器归零的最佳方法是将128位寄存器与其自身相连,并依赖于零扩展。你是对的,128位VEX编码指令缩短了一到两个字节。

大多数处理器认识到寄存器的xor与其自身独立于寄存器的先前值。


12
2018-05-03 05:32



我已经在Google Compute Engine上对Skylake-avx512上的vxorps进行了测试。他们的KVM虚拟机没有可用的perf计数器或CPU频率,但是时间结果(来自该测试和其他测试)表明了这一点 vxorps zmm 降低最大涡轮增压(并且可能触发512b执行单元的预热),同时 vxorps xmm 要么 ymm 别。如果它已经解码为多个uops,它运行速度会慢得多。我不被允许发表基准,但我想我可以这么说。当与其他AVX512指令混合时,这当然与指令选择无关。 - Peter Cordes


答案:


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

今天唯一支持AVX512的处理器是Knights Landing。它使用单个微操作来对xmm寄存器进行xor。通过将它分成两部分来处理矢量大小的新扩展是很常见的。这发生在从64位到128位的转换以及从128位到256位的转换。未来某些处理器(来自AMD或Intel或任何其他供应商)很可能会将512位向量分成两个256位向量甚至四个128位向量。因此,将zmm寄存器归零的最佳方法是将128位寄存器与其自身相连,并依赖于零扩展。你是对的,128位VEX编码指令缩短了一到两个字节。

大多数处理器认识到寄存器的xor与其自身独立于寄存器的先前值。


12
2018-05-03 05:32



我已经在Google Compute Engine上对Skylake-avx512上的vxorps进行了测试。他们的KVM虚拟机没有可用的perf计数器或CPU频率,但是时间结果(来自该测试和其他测试)表明了这一点 vxorps zmm 降低最大涡轮增压(并且可能触发512b执行单元的预热),同时 vxorps xmm 要么 ymm 别。如果它已经解码为多个uops,它运行速度会慢得多。我不被允许发表基准,但我想我可以这么说。当与其他AVX512指令混合时,这当然与指令选择无关。 - Peter Cordes