2015-10-15 15 views
10

Ich habe ein seltsames Problem mit einigen SSE2 und AVX-Code, an denen ich gearbeitet habe. Ich baue meine Anwendung mit GCC, die Laufzeit-CPU-Erkennung. Die Objektdateien werden mit separaten Flags für jede CPU-Funktion, beispielsweise gebaut:SSE läuft langsam nach der Verwendung von AVX

g++ -c -o ConvertSamples_SSE.o ConvertSamples_SSE.cpp -std=c++11 -fPIC -O0 -g -Wall -I./include -msse 
g++ -c -o ConvertSamples_SSE2.o ConvertSamples_SSE2.cpp -std=c++11 -fPIC -O0 -g -Wall -I./include -msse2 
g++ -c -o ConvertSamples_AVX.o ConvertSamples_AVX.cpp -std=c++11 -fPIC -O0 -g -Wall -I./include -mavx 

Als ich zum ersten Mal das Programm starten, finde ich, dass die SSE2-Routinen sind wie für normale mit einem schönen Geschwindigkeitsschub gegenüber den nicht SSE-Routinen (um 100% schneller). Nachdem ich irgendeine AVX-Routine ausgeführt habe, läuft die exakt gleiche SSE2-Routine jetzt viel langsamer.

Könnte jemand bitte erklären, was die Ursache dafür sein könnte?

Bevor die AVX-Routine läuft, sind alle Tests um 80-130% schneller als die FPU-Mathematik, wie hier zu sehen ist, nachdem die AVX-Routine läuft, sind die SSE-Routinen viel langsamer.

Wenn ich die AVX-Testroutinen überspringe, sehe ich nie diesen Leistungsverlust.

Hier ist meine SSE2 Routine

void Float_S16(const float *in, int16_t *out, const unsigned int samples) 
{ 
    static float ratio = (float)Limits<int16_t>::range()/(float)Limits<float>::range(); 
    static __m128 mul = _mm_set_ps1(ratio); 

    unsigned int i; 
    for (i = 0; i < samples - 3; i += 4, in += 4, out += 4) 
    { 
    __m128i con = _mm_cvtps_epi32(_mm_mul_ps(_mm_load_ps(in), mul)); 
    out[0] = ((int16_t*)&con)[0]; 
    out[1] = ((int16_t*)&con)[2]; 
    out[2] = ((int16_t*)&con)[4]; 
    out[3] = ((int16_t*)&con)[6]; 
    } 

    for (; i < samples; ++i, ++in, ++out) 
    *out = (int16_t)lrint(*in * ratio); 
} 

Und die AVX-Version des gleichen.

void Float_S16(const float *in, int16_t *out, const unsigned int samples) 
{ 
    static float ratio = (float)Limits<int16_t>::range()/(float)Limits<float>::range(); 
    static __m256 mul = _mm256_set1_ps(ratio); 

    unsigned int i; 
    for (i = 0; i < samples - 7; i += 8, in += 8, out += 8) 
    { 
    __m256i con = _mm256_cvtps_epi32(_mm256_mul_ps(_mm256_load_ps(in), mul)); 
    out[0] = ((int16_t*)&con)[0]; 
    out[1] = ((int16_t*)&con)[2]; 
    out[2] = ((int16_t*)&con)[4]; 
    out[3] = ((int16_t*)&con)[6]; 
    out[4] = ((int16_t*)&con)[8]; 
    out[5] = ((int16_t*)&con)[10]; 
    out[6] = ((int16_t*)&con)[12]; 
    out[7] = ((int16_t*)&con)[14]; 
    } 

    for(; i < samples; ++i, ++in, ++out) 
    *out = (int16_t)lrint(*in * ratio); 
} 

Ich habe auch dies durch Valgrind ausgeführt, die keine Fehler erkennt.

+1

Wie wird die Zeit gemessen? – Gilles

+0

@Gilles mit 'clock_gettime (CLOCK_MONOTONIC, & Start);' vorher und nachher, dann Berechnung der Differenz. – Geoffrey

+0

Ich bin auf seltsame Probleme mit gemischten SSEX und AVX-Code gestoßen ..., vor allem weil Link Time Code Generation/etc. Probleme. Schauen Sie sich Ihre Assembly-Dateien an (und veröffentlichen Sie sie vielleicht). – Christopher

Antwort

15

Das Mischen von AVX-Code und altem SSE-Code verursacht eine Leistungseinbuße. Die sinnvollste Lösung ist die Ausführung der VZEROALL-Anweisung nach einem AVX-Codeabschnitt, insbesondere kurz vor der Ausführung des SSE-Codes.

Gemäß Intel-Diagramm liegt die Strafe beim Übergang in oder aus Zustand C (Legacy-SSE mit der oberen Hälfte der gespeicherten AVX-Register) in der Größenordnung von 100 Taktzyklen. Die anderen Übergänge sind nur 1 Zyklus:

Referenzen:

+2

Dieses Problem kann drastische Auswirkungen haben, die mit dieser Strafe nichts zu tun haben. Siehe [diese Frage] (http://stackoverflow.com/q/21960229/2542702), wo das OP eine Geschwindigkeit mit über 500 Threads auf einem System mit nur acht Hyper-Threads sah. –