Cómo usar las instrucciones Fused Multiply-Add (FMA) con SSE / AVX

Aprendí que algunas CPU Intel / AMD pueden multiplicarse simultáneamente y agregarse con SSE / AVX:
FLOPS por ciclo para sandy-bridge y haswell SSE2 / AVX / AVX2 .

Me gusta saber cómo hacer esto mejor en el código y también quiero saber cómo se hace internamente en la CPU. Me refiero a la architecture súper escalar. Digamos que quiero hacer una sum larga como la siguiente en SSE:

//sum = a1*b1 + a2*b2 + a3*b3 +... where a is a scalar and b is a SIMD vector (eg from matrix multiplication) sum = _mm_set1_ps(0.0f); a1 = _mm_set1_ps(a[0]); b1 = _mm_load_ps(&b[0]); sum = _mm_add_ps(sum, _mm_mul_ps(a1, b1)); a2 = _mm_set1_ps(a[1]); b2 = _mm_load_ps(&b[4]); sum = _mm_add_ps(sum, _mm_mul_ps(a2, b2)); a3 = _mm_set1_ps(a[2]); b3 = _mm_load_ps(&b[8]); sum = _mm_add_ps(sum, _mm_mul_ps(a3, b3)); ... 

Mi pregunta es ¿cómo se convierte esto a multiplicar simultáneamente y agregar? ¿Pueden los datos ser dependientes? Quiero decir, ¿puede la CPU hacer _mm_add_ps(sum, _mm_mul_ps(a1, b1)) simultáneamente o hacer los registros usados ​​en la multiplicación y agregar tener que ser independiente?

Por último, ¿cómo se aplica esto a FMA (con Haswell)? ¿ _mm_add_ps(sum, _mm_mul_ps(a1, b1)) se convierte automáticamente en una sola instrucción FMA o microoperación?

El comstackdor puede fusionar un complemento separado y multiplicar, aunque esto cambie el resultado final (haciéndolo más preciso).

Una FMA tiene solo un redondeo (efectivamente mantiene una precisión infinita para el resultado de multiplicación temporal interna), mientras que un ADD + MUL tiene dos.

Los estándares IEEE y C permiten esto cuando #pragma STDC FP_CONTRACT ON está en vigor, y los comstackdores pueden tenerlo ON por defecto (pero no todos lo hacen). Gcc contrata por defecto a FMA (con el valor predeterminado -std=gnu* , pero no -std=c* , por ejemplo -std=c++14 ). Para Clang , solo está habilitado con -ffp-contract=fast . (Con solo #pragma habilitado, solo dentro de una sola expresión como a+b*c , no en declaraciones de C ++ separadas).

Esto es diferente del punto flotante estricto vs. relajado (o en términos de gcc, -ffast-math vs. -fno-fast-math ) que permitiría otros tipos de optimizaciones que podrían boost el error de redondeo en función de los valores de entrada . Este es especial debido a la precisión infinita del interno temporal de FMA; si hubo algún redondeo en el temporal interno, esto no estaría permitido en un FP estricto.

Incluso si habilita el punto flotante relajado, el comstackdor aún puede optar por no fusionarse, ya que podría esperar que sepa lo que está haciendo si ya está utilizando intrínsecos.


Entonces, la mejor manera de asegurarse de que realmente reciba las instrucciones FMA que desea es que realmente use las características intrínsecas proporcionadas para ellas:

Intrínsecos FMA3: (AVX2 – Intel Haswell)

  • _mm_fmadd_pd() , _ mm256_fmadd_pd()
  • _mm_fmadd_ps() , _mm256_fmadd_ps()
  • y sobre un montón de otras variaciones …

Intrínsecos FMA4: (XOP – AMD Bulldozer)

  • _mm_macc_pd() , _mm256_macc_pd()
  • _mm_macc_ps() , _mm256_macc_ps()
  • y sobre un montón de otras variaciones …

Probé el siguiente código en GCC 5.3, Clang 3.7, ICC 13.0.1 y MSVC 2015 (comstackción versión 19.00).

 float mul_add(float a, float b, float c) { return a*b + c; } __m256 mul_addv(__m256 a, __m256 b, __m256 c) { return _mm256_add_ps(_mm256_mul_ps(a, b), c); } 

Con las opciones correctas del comstackdor (ver a continuación) cada comstackdor generará una instrucción vfmadd (por ejemplo, vfmadd213ss ) de mul_add . Sin embargo, solo MSVC no puede contratar mul_addv a una sola instrucción vfmadd (por ejemplo, vfmadd213ps ).

Las siguientes opciones de comstackción son suficientes para generar instrucciones vfmadd (excepto con mul_addv con mul_addv ).

 GCC: -O2 -mavx2 -mfma Clang: -O1 -mavx2 -mfma -ffp-contract=fast ICC: -O1 -march=core-avx2 MSVC: /O1 /arch:AVX2 /fp:fast 

GCC 4.9 no contratará mul_addv con una sola instrucción fma, pero al menos desde GCC 5.1 lo hace. No sé cuándo los otros comstackdores comenzaron a hacer esto.