2016-11-04 1 views
3

Ich benutze Intel SSE/AVX/FMA intrinsics, um perfekt inline SSE/AVX Anweisungen für einige mathematische Funktionen zu erreichen.Probleme von Compiler generiert Assembly für intrinsics

den folgenden Code

#include <cmath> 
#include <immintrin.h> 

auto std_fma(float x, float y, float z) 
{ 
    return std::fma(x, y, z); 
} 

float _fma(float x, float y, float z) 
{ 
    _mm_store_ss(&x, 
     _mm_fmadd_ss(_mm_load_ss(&x), _mm_load_ss(&y), _mm_load_ss(&z)) 
    ); 

    return x; 
} 

float _sqrt(float x) 
{ 
    _mm_store_ss(&x, 
     _mm_sqrt_ss(_mm_load_ss(&x)) 
    ); 

    return x; 
} 

das Klirren 3.9 generierte Assembly mit -march = x86-64 Bei -mfma O3

std_fma(float, float, float):       # @std_fma(float, float, float) 
     vfmadd213ss  xmm0, xmm1, xmm2 
     ret 

_fma(float, float, float):        # @_fma(float, float, float) 
     vxorps xmm3, xmm3, xmm3 
     vmovss xmm0, xmm3, xmm0  # xmm0 = xmm0[0],xmm3[1,2,3] 
     vmovss xmm1, xmm3, xmm1  # xmm1 = xmm1[0],xmm3[1,2,3] 
     vmovss xmm2, xmm3, xmm2  # xmm2 = xmm2[0],xmm3[1,2,3] 
     vfmadd213ss  xmm0, xmm1, xmm2 
     ret 

_sqrt(float):        # @_sqrt(float) 
     vsqrtss xmm0, xmm0, xmm0 
     ret 

während der generierte Code für _sqrt in Ordnung ist, gibt sind unnötig vxorps (die das absolut unbenutzte xmm3 Register auf Null setzt) ​​und movss Anweisungen in _fma im Vergleich zu std_fma (die r ely auf Compiler intrinsische std :: fma)

der GCC 6.2 generierte Assembly mit -march = x86-64 -mfma O3

std_fma(float, float, float): 
     vfmadd132ss  xmm0, xmm2, xmm1 
     ret 
_fma(float, float, float): 
     vinsertps  xmm1, xmm1, xmm1, 0xe 
     vinsertps  xmm2, xmm2, xmm2, 0xe 
     vinsertps  xmm0, xmm0, xmm0, 0xe 
     vfmadd132ss  xmm0, xmm2, xmm1 
     ret 
_sqrt(float): 
     vinsertps  xmm0, xmm0, xmm0, 0xe 
     vsqrtss xmm0, xmm0, xmm0 
     ret 

und hier sind eine Menge unnötiger vinsertps Anweisungen

Arbeitsbeispiel: https://godbolt.org/g/q1BQym

Der Standard-x64 Aufruf Pass convention-Floating-Point-Funktionsargumente in XMM regi sters, so dass diese vmovss und vinsertps Anweisungen sollten beseitigt werden. Warum senden die genannten Compiler sie noch? Ist es möglich, sie ohne Inline-Montage loszuwerden?

Ich habe auch versucht, _mm_cvtss_f32 anstelle von _mm_store_ss und mehrere Aufrufkonventionen zu verwenden, aber nichts geändert.

+3

Das Ergebnis des intrinsischen '_mm_load_ss' ist ein 128-Bit-Vektor mit dem 32-Bit Gleitkommawert im ersten Element und Nullen in den anderen drei Elementen. Das tun die unnötigen Befehle und setzen die anderen drei Elemente auf Null. Die Compiler sind nicht schlau genug, um zu erkennen, dass diese Elemente nie verwendet und letztendlich verworfen werden, wenn die Funktion zurückkehrt, aber sie tun, was Sie von ihr verlangt haben. Es scheint, dass Sie bereits die perfekte Lösung für den FMA-Fall haben. –

+0

Das ist wirklich schlecht, die Compiler sollten das wissen, da ich '* _ss' intrinsics verwende. – plasmacel

+0

AFAIK, ist die einzige Lösung, das nicht zu tun (und ich denke, das ist ein Duplikat von http://StackOverflow.com/questions/39318496/How-to-merge-a-Scalar-into-Avector-without- the-Compiler-Verschwendung-eine-Anweisung). Clang sieht in einigen Fällen, dass die oberen Elemente unbenutzt sind und vermeiden können, sie zu berühren (siehe diese verbundene Frage). Sie können den Compiler dazu bringen, FMA zu verwenden, wenn dies für skalaren Code mit einer Option (nicht nur '-mfma' oder' -ffast-math') möglich ist, aber ich vergesse, was und habe keine Zeit, es jetzt zu suchen. Da 'std :: fma' Inlines perfekt ist, benutze es einfach. –

Antwort

2

Ich schreibe diese Antwort basierend auf den Kommentaren, einigen Diskussionen und meinen eigenen Erfahrungen. Wie Ross Ridge in den Kommentaren darauf hingewiesen hat, ist der Compiler nicht schlau genug, um zu erkennen, dass nur das niedrigste Gleitkomma-Element des XMM-Registers verwendet wird, so dass die anderen drei Elemente mit diesen vxorps -Anweisungen auf Null gesetzt werden . Das ist absolut unnötig, aber was können Sie tun?

Notwendigkeit zu beachten, dass Klirren 3.9 tut viel besser als GCC 6.2 (oder aktuelle Momentaufnahme von 7,0) bei der Montage für Intel-Spezifika zu erzeugen, da es sich bei _mm_fmadd_ss in meinem Beispiel nicht nur. Ich testete auch mehr intrinsics und in den meisten Fällen clang tat perfekte Arbeit, um einzelne Anweisungen zu emittieren.

Was können Sie

tun können Sie die Standard-<cmath> Funktionen verwenden, mit der Hoffnung, dass sie als Compiler intrinsics definiert werden, wenn ein geeigneter CPU-Befehle zur Verfügung.

Dies ist nicht genug

Compiler, wie GCC diese Funktionen mit Sonderbehandlung von NaN und Unendlichkeiten implementieren.So können sie zusätzlich zu den intrinsics einige Vergleichs-, Verzweigungs- und mögliche errno Flaghandling tun.

Compiler-Flags -fno-math-errno-fno-trapping-math helfen GCC und Klirren die zusätzlichen Floating-Point-Sonderfälle und errno Handhabung zu beseitigen, so können sie einzelne Anweisungen, wenn möglich emittieren: https://godbolt.org/g/LZJyaB.

Sie können das gleiche mit -ffast-math erreichen, da es auch die oben genannten Flags enthält, aber es includes much more than that, und diejenigen (wie unsichere mathematische Optimierungen) sind wahrscheinlich nicht erwünscht.

Leider ist dies keine tragbare Lösung. Es funktioniert in den meisten Fällen (siehe den Link godbolt), aber Sie sind immer noch auf die Implementierung angewiesen.

Was mehr

Sie können noch Inline-Assembly verwenden, die auch nicht tragbar ist, viel komplizierter und es gibt viel mehr Dinge zu beachten. Trotzdem kann es für solche einfachen einzeiligen Anweisungen in Ordnung sein.

Dinge zu beachten:

1.GCC/Klirren und Visual Studio verwenden unterschiedliche Syntax für die Inline-Montage und Visual Studio in x64-Modus nicht zulässt.

2. Sie müssen VEX codierte Befehle (3 op Varianten, z.B. vsqrtss xmm0 xmm1 xmm2) für AVX Ziele und nicht-kodierte VEX (2 op Varianten, z.B. sqrtss xmm0 xmm1) Varianten für pre-AVX CPUs emittieren. VEX-codierte Befehle sind 3 Operandenanweisungen, so dass sie dem Compiler mehr Freiheit bei der Optimierung bieten. Um diesen Vorteil zu nutzen, muss register input/output parameters richtig eingestellt sein. So etwas wie unten macht den Job.

# if __AVX__ 
    asm("vsqrtss %1, %1, %0" :"=x"(x) : "x"(x)); 
# else 
    asm("sqrtss %1, %0" :"=x"(x) : "x"(x)); 
# endif 

Aber die folgende ist eine schlechte Technik für VEX:

asm("vsqrtss %1, %1, %0" :"+x"(x)); 

Es zu einem unnötigen Bewegungsbefehl ergeben kann, überprüfen https://godbolt.org/g/VtNMLL.

3. Wie Peter Cordes darauf hingewiesen hat, können Sie common subexpression elimination (CSE) und constant folding (constant propagation) für Inline-Assembly-Funktionen verlieren. Wenn jedoch die Inline-Asm nicht als volatile deklariert ist, kann der Compiler sie als eine reine Funktion behandeln, die nur von ihren Eingaben abhängt und eine gemeinsame Teilausdruck-Eliminierung durchführt, was großartig ist.

Wie Peter sagte:

Don't use inline asm keine absolute Regel ist, es ist nur etwas, das Sie sorgfältig haben und berücksichtigen vor der Verwendung sein sollte. Wenn die Alternativen nicht Ihren Anforderungen entsprechen, und Sie nicht mit dieses Inlining an Orten, wo es nicht optimieren kann, enden, dann gehen Sie rechts voraus.

Verwandte Themen