title: Haswell AVX/FMA latencies tested 1 cycle slower than Intel's guide says
body: <p>In Intel Intrinsics Guide, <code>vmulpd</code> and <code>vfmadd213pd</code> has latency of 5, <code>vaddpd</code> has latency of 3.</p>
  <p>I write some test code, but all of the results are 1 cycle slower.</p>
  <p>Here is my test code:</p>
  <pre><code>.CODE
  test_latency PROC
      vxorpd  ymm0, ymm0, ymm0
      vxorpd  ymm1, ymm1, ymm1
  
  loop_start:
      vmulpd  ymm0, ymm0, ymm1
      vmulpd  ymm0, ymm0, ymm1
      vmulpd  ymm0, ymm0, ymm1
      vmulpd  ymm0, ymm0, ymm1
      sub     rcx, 4
      jg      loop_start
  
      ret
  test_latency ENDP
  END
  </code></pre>
  <pre class="lang-cpp prettyprint-override"><code>#include &lt;stdio.h&gt;
  #include &lt;omp.h&gt;
  #include &lt;stdint.h&gt;
  #include &lt;windows.h&gt;
  
  extern &quot;C&quot; void test_latency(int64_t n);
  
  int main()
  {
      SetThreadAffinityMask(GetCurrentThread(), 1);   // Avoid context switch
      
      int64_t n = (int64_t)3e9;
      double start = omp_get_wtime();
      test_latency(n);
      double end = omp_get_wtime();
      double time = end - start;
      
      double freq = 3.3e9;    // My CPU frequency
      double latency = freq * time / n;
      printf(&quot;latency = %f\n&quot;, latency);
  }
  </code></pre>
  <p>My CPU is Core i5 4590, I locked its frequency at 3.3GHz. The output is: <code>latency = 6.102484</code>.</p>
  <p>Strange enough, if I change <code>vmulpd  ymm0, ymm0, ymm1</code> to <code>vmulpd  ymm0, ymm0, ymm0</code>, then the output become: <code>latency = 5.093745</code>.</p>
  <p>Is there an explanation? Is my test code problematic?</p>
  <p><strong>MORE RESULTS</strong></p>
  <pre><code>results on Core i5 4590 @3.3GHz
  vmulpd  ymm0, ymm0, ymm1       6.056094
  vmulpd  ymm0, ymm0, ymm0       5.054515
  vaddpd  ymm0, ymm0, ymm1       4.038062
  vaddpd  ymm0, ymm0, ymm0       3.029360
  vfmadd213pd ymm0, ymm0, ymm1   6.052501
  vfmadd213pd ymm0, ymm1, ymm0   6.053163
  vfmadd213pd ymm0, ymm1, ymm1   6.055160
  vfmadd213pd ymm0, ymm0, ymm0   5.041532
  
  (without vzeroupper)
  vmulpd  xmm0, xmm0, xmm1       6.050404
  vmulpd  xmm0, xmm0, xmm0       5.042191
  vaddpd  xmm0, xmm0, xmm1       4.044518
  vaddpd  xmm0, xmm0, xmm0       3.024233
  vfmadd213pd xmm0, xmm0, xmm1   6.047219
  vfmadd213pd xmm0, xmm1, xmm0   6.046022
  vfmadd213pd xmm0, xmm1, xmm1   6.052805
  vfmadd213pd xmm0, xmm0, xmm0   5.046843
  
  (with vzeroupper)
  vmulpd  xmm0, xmm0, xmm1       5.062350
  vmulpd  xmm0, xmm0, xmm0       5.039132
  vaddpd  xmm0, xmm0, xmm1       3.019815
  vaddpd  xmm0, xmm0, xmm0       3.026791
  vfmadd213pd xmm0, xmm0, xmm1   5.043748
  vfmadd213pd xmm0, xmm1, xmm0   5.051424
  vfmadd213pd xmm0, xmm1, xmm1   5.049090
  vfmadd213pd xmm0, xmm0, xmm0   5.051947
  
  (without vzeroupper)
  mulpd   xmm0, xmm1             5.047671
  mulpd   xmm0, xmm0             5.042176
  addpd   xmm0, xmm1             3.019492
  addpd   xmm0, xmm0             3.028642
  
  (with vzeroupper)
  mulpd   xmm0, xmm1             5.046220
  mulpd   xmm0, xmm0             5.057278
  addpd   xmm0, xmm1             3.025577
  addpd   xmm0, xmm0             3.031238
  
  </code></pre>
  <p><strong>MY GUESS</strong></p>
  <p>I changed <code>test_latency</code> like this:</p>
  <pre><code>.CODE
  test_latency PROC
      vxorpd  ymm0, ymm0, ymm0
      vxorpd  ymm1, ymm1, ymm1
  
  loop_start:
      vaddpd  ymm1, ymm1, ymm1  ; added this line
      vmulpd  ymm0, ymm0, ymm1
      vmulpd  ymm0, ymm0, ymm1
      vmulpd  ymm0, ymm0, ymm1
      vmulpd  ymm0, ymm0, ymm1
      sub     rcx, 4
      jg      loop_start
  
      ret
  test_latency ENDP
  END
  </code></pre>
  <p>Finally I get the result of 5 cycle. There are other instructions to achieve the same effect:</p>
  <pre><code>vmovupd     ymm1, ymm0
  vmovupd     ymm1, [mem]
  vmovdqu     ymm1, [mem]
  vxorpd      ymm1, ymm1, ymm1
  vpxor       ymm1, ymm1, ymm1
  vmulpd      ymm1, ymm1, ymm1
  vshufpd     ymm1, ymm1, ymm1, 0
  </code></pre>
  <p>But these instructions cannot:</p>
  <pre><code>vmovupd     ymm1, ymm2  ; suppose ymm2 is zeroed
  vpaddq      ymm1, ymm1, ymm1
  vpmulld     ymm1, ymm1, ymm1
  vpand       ymm1, ymm1, ymm1
  </code></pre>
  <p>In the case of ymm instructions, I guess the conditions to avoid 1 extra cycle are:</p>
  <ol>
  <li>All inputs are from the same domain.</li>
  <li>All inputs are fresh enough. (move from old value doesn't work)</li>
  </ol>
  <p>As for VEX xmm, the condition seems a little blur. It seems related to upper half state, but I don't know which one is cleaner:</p>
  <pre><code>vxorpd      ymm1, ymm1, ymm1
  vxorpd      xmm1, xmm1, xmm1
  vzeroupper
  </code></pre>
  <p>Hard question to me.</p>