Jak używać instrukcji Fused Multiply-Add (FMA) z SSE / AVX

Dowiedziałem się, że niektóre procesory Intel / AMD potrafią jednocześnie mnożyć i dodawać z SSE / AVX:
FLOPS na cykl dla sandy-bridge i haswell SSE2/AVX / AVX2.

Lubię wiedzieć, jak zrobić to najlepiej w kodzie, a także chcę wiedzieć, jak to jest zrobione wewnętrznie w procesorze. Mam na myśli super-skalarną architekturę. Załóżmy, że chcę wykonać długą sumę, taką jak poniżej w SSE:

//sum = a1*b1 + a2*b2 + a3*b3 +... where a is a scalar and b is a SIMD vector (e.g. 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));
...

Moje pytanie brzmi jak to się konwertuje do jednoczesnego mnożenia i dodawania? Can dane są zależne? Chodzi mi o to, czy procesor może wykonywać _mm_add_ps(sum, _mm_mul_ps(a1, b1)) jednocześnie, czy rejestry używane w mnożeniu i dodawaniu muszą być niezależne?

Wreszcie, jak to ma zastosowanie do FMA (z Haswell)? Czy _mm_add_ps(sum, _mm_mul_ps(a1, b1)) jest automatycznie konwertowana na pojedynczą instrukcję FMA lub mikroprocesor?

Author: Community, 2013-04-10

2 answers

Kompilator może łączyć oddzielone dodawanie i mnożenie, mimo że zmienia to ostateczny wynik (czyniąc go bardziej dokładnym).

FMA ma tylko jedno zaokrąglenie (skutecznie zachowuje nieskończoną precyzję dla wewnętrznego tymczasowego wyniku mnożenia), podczas gdy ADD + MUL ma dwa.

Standardy IEEE i C pozwalają na to, gdy #pragma STDC FP_CONTRACT ON jest w mocy, a Kompilatory mogą mieć to ON domyślnie (ale nie wszystkie tak robią). Gcc Kontrakty do FMA domyślnie (z domyślne -std=gnu*, ale nie -std=c*, np. -std=c++14). dla Clang , jest on włączony tylko przez -ffp-contract=fast. (Z włączonym tylko #pragma, tylko w obrębie jednego wyrażenia, takiego jak a+b*c, a nie w obrębie oddzielnych instrukcji C++.).

To różni się od strict vs. relaxed zmiennoprzecinkowy (lub w terminach gcc, -ffast-math vs. -fno-fast-math), które pozwalają na inne rodzaje optymalizacji , które mogłyby zwiększyć błąd zaokrąglania w zależności od wartości wejściowych. Ten jest wyjątkowy ze względu na nieskończoną precyzja wewnętrznego tymczasowego FMA; gdyby w wewnętrznym tymczasowym było jakiekolwiek zaokrąglenie, nie byłoby to dozwolone w ścisłym FP.

Nawet jeśli włączysz relaxed floating-point, kompilator może nadal zdecydować się nie łączyć, ponieważ może oczekiwać, że wiesz, co robisz, jeśli już używasz intrinsics.


Więc najlepszym sposobem aby upewnić się, że otrzymujesz instrukcje FMA, które chcesz, jest użycie dostarczonych elementów wewnętrznych dla oni:

FMA3: (AVX2-Intel Haswell)

  • _mm_fmadd_pd(), _mm256_fmadd_pd()
  • _mm_fmadd_ps(), _mm256_fmadd_ps()
  • I O gazillion innych odmian...

FMA4: (XOP-AMD Bulldozer)

  • _mm_macc_pd(), _mm256_macc_pd()
  • _mm_macc_ps(), _mm256_macc_ps()
  • I O gazillion innych odmian...
 35
Author: Mysticial,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-09-08 19:34:02

Testowałem następujący kod w GCC 5.3, Clang 3.7, ICC 13.0.1 i MSVC 2015 (wersja kompilatora 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);
}

Z odpowiednimi opcjami kompilatora (patrz poniżej) każdy kompilator wygeneruje instrukcję vfmadd (np. vfmadd213ss) z mul_add. Jednak tylko MSVC nie zleca mul_addv pojedynczej instrukcji vfmadd (np. vfmadd213ps).

Następujące opcje kompilatora są wystarczające do wygenerowania instrukcji vfmadd (z wyjątkiem mul_addv z MSVC).

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 nie będzie kontrakt mul_addv na pojedynczą instrukcję fma, ale od co najmniej GCC 5.1 to robi. Nie wiem, kiedy inne Kompilatory zaczęły to robić.

 11
Author: Z boson,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-12-25 10:01:59