title: Is vxorps-zeroing on AMD Jaguar/Bulldozer/Zen faster with xmm registers than ymm?
body: <p>AMD CPUs handle 256b AVX instructions by decoding into two 128b operations.  e.g. <code>vaddps ymm0, ymm1,ymm1</code> on AMD Steamroller decodes to 2 macro-ops, with half the throughput of <code>vaddps xmm0, xmm1,xmm1</code>.</p>
  
  <p>XOR-zeroing is a special case (no input dependency, and <a href="http://www.realworldtech.com/jaguar/4/" rel="noreferrer">on Jaguar at least avoids consuming a physical register file entry</a>, and enables movdqa from that register to be eliminated at issue/rename, like Bulldozer does all the time even for non-zerod regs).  <strong>But is it detected early enough that <code>vxorps ymm0,ymm0,ymm0</code> still only decodes to 1 macro-op with equal performance to  <code>vxorps xmm0,xmm0,xmm0</code>?  (unlike <code>vxorps ymm3, ymm2,ymm1</code>)</strong></p>
  
  <p>Or does independence-detection happen later, after already decoding into two uops?  Also, does vector xor-zeroing on AMD CPUs still use an execution port?  On Intel-CPUs, Nehalem needs a port but Sandybridge-family handles it in the issue/rename stage.</p>
  
  <p>Agner Fog's instruction tables don't list this special-case, and his microarch guide doesn't mention the number of uops.</p>
  
  <hr>
  
  <p>This could mean <code>vxorps xmm0,xmm0,xmm0</code> is a better way to implement <code>_mm256_setzero_ps()</code>.</p>
  
  <p>For AVX512, <code>_mm512_setzero_ps()</code> also saves a byte by using only a VEX-coded zeroing idiom, rather than EVEX, when possible.  (i.e. for zmm0-15.  <code>vxorps xmm31,xmm31,xmm31</code> would still require an EVEX).  gcc/clang currently use xor-zeroing idioms of whatever register-width they want, rather than always using AVX-128.</p>
  
  <p>Reported as clang <a href="https://bugs.llvm.org/show_bug.cgi?id=32862" rel="noreferrer">bug 32862</a> and gcc <a href="https://gcc.gnu.org/bugzilla/show_bug.cgi?id=80636" rel="noreferrer">bug 80636</a>.  MSVC already uses <code>xmm</code>.  Not yet reported to ICC, which also uses zmm regs for AVX512 zeroing.  (Although Intel might not care to change since there's currently no benefit on any Intel CPUs, only AMD.  If they ever release a low-power CPU that splits vectors in half, they might.  Their current low-power deisgn (Silvermont) doesn't support AVX at all, only SSE4.)</p>
  
  <hr>
  
  <p>The only possible downside I know of to using an AVX-128 instruction for zeroing a 256b register is that it doesn't trigger warm-up of the 256b execution units on Intel CPUs.  Possibly defeating a C or C++ hack that tries to warm them up.</p>
  
  <p>(256b vector instructions are slower for the first ~56k cycles after the first 256b instruction.  See the Skylake section in Agner Fog's microarch pdf).  It's probably ok if calling a <code>noinline</code> function that returns <code>_mm256_setzero_ps</code> isn't a reliable way to warm up the execution units.  (One that still works without AVX2, and avoids any loads (that could cache miss) is <code>__m128 onebits = _mm_castsi128_ps(_mm_set1_epi8(0xff));</code><br>
  <code>return _mm256_insertf128_ps(_mm256_castps128_ps256(onebits), onebits)</code> which should compile to <code>pcmpeqd xmm0,xmm0,xmm0</code> / <code>vinsertf128 ymm0,xmm0,1</code>.  That's still pretty trivial for something you call once to warm-up (or keep warm) the execution units well ahead of a critical loop.  And if you want something that can inline, you probably need inline-asm.)</p>
  
  <hr>
  
  <p>I don't have AMD hardware so I can't test this.</p>
  
  <p>If anyone has AMD hardware but doesn't know how to test, use perf counters to count cycles (and preferably m-ops or uops or whatever AMD calls them).</p>
  
  <p>This is the NASM/YASM source I use to test short sequences:</p>
  
  <pre><code>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
  </code></pre>
  
  <p>If you're not on Linux, maybe replace the stuff after the loop (the exit syscall) with a <code>ret</code>, and call the function from a C <code>main()</code> function.</p>
  
  <p>Assemble with <code>nasm -felf64 vxor-zero.asm &amp;&amp; ld -o vxor-zero vxor-zero.o</code> to make a static binary.  (Or use <a href="https://stackoverflow.com/questions/36861903/assembling-32-bit-binaries-on-a-64-bit-system-gnu-toolchain/36901649#36901649">the <code>asm-link</code> script I posted in a Q&amp;A about assembling static/dynamic binaries with/without libc</a>).</p>
  
  <p>Example output on an i7-6700k (Intel Skylake), at 3.9GHz.  (IDK why my machine only goes up to 3.9GHz after it's been idle a few minutes.  Turbo up to 4.2 or 4.4GHz works normally right after boot).  Since I'm using perf counters, it doesn't actually matter what clock speed the machine is running.  No loads/stores or code-cache misses are involved, so the number of core-clock-cycles for everything is constant regardless of how long they are.</p>
  
  <pre><code>$ alias disas='objdump -drwC -Mintel'
  $ b=vxor-zero;  asm-link "$b.asm" &amp;&amp; disas "$b" &amp;&amp; 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 &lt;_start&gt;:
    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 &lt;_start.loop&gt;:
    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 &lt;_start.loop&gt;
    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% )
  </code></pre>
  
  <p>The +- 0.02% stuff is because I ran <code>perf stat -r4</code>, so it ran my binary 4 times.</p>
  
  <p><code>uops_issued_any</code> and <code>uops_retired_retire_slots</code> are fused-domain (front-end throughput limit of 4 per clock on Skylake and Bulldozer-family).  The counts are nearly identical because there are no branch mispredicts (which lead to speculatively-issued uops being discarded instead of retired).</p>
  
  <p><code>uops_executed_thread</code> is unfused-domain uops (execution ports).  <a href="https://stackoverflow.com/questions/33666617/what-is-the-best-way-to-set-a-register-to-zero-in-x86-assembly-xor-mov-or-and/33668295#33668295">xor-zeroing doesn't need any on Intel CPUs</a>, so it's just the dec and branch uops that actually execute.  (If we changed the operands to vxorps so it wasn't just zeroing a register, e.g. <code>vxorps ymm2, ymm1,ymm0</code> to write the output to a register that the next one doesn't read, uops executed will match the fused-domain uop count.  And we'd see that the throughput limit is three vxorps per clock.)</p>
  
  <p>2000M fused-domain uops issued in 500M clock cycles is 4.0 uops issued per clock: achieving the theoretical max front-end throughput.  6 * 250 is 1500, so these counts match with Skylake decoding <code>vxorps ymm,ymm,ymm</code> to 1 fused-domain uop.</p>
  
  <p>With a different number of uops in the loop, things aren't as good.  e.g. a 5 uop loop only issued at 3.75 uops per clock.  I intentionally chose this to be 8 uops (when vxorps decodes to a single-uop).</p>
  
  <p>The issue-width of Zen is 6 uops per cycle, so it may do better with a different amount of unrolling.  (See <a href="https://stackoverflow.com/questions/39311872/is-performance-reduced-when-executing-loops-whose-uop-count-is-not-a-multiple-of">this Q&amp;A</a> for more about short loops whose uop count isn't a multiple of the issue width, on Intel SnB-family uarches).</p>