Frage

Ich bin ein gestreutes Lese von 8-Bit-Daten aus einer Datei (De-Interleaving eine 64-Kanal-Wave-Datei) durchführen. Ich kombiniere sie dann ein einziger Strom von Bytes zu sein. Das Problem, das ich habe, ist mit meiner Rekonstruktion der Daten zu schreiben.

Im Grunde genommen bin ich in 16 Byte zu lesen und sie dann in einem einzigen __m128i variablen Aufbau und dann _mm_stream_ps über den Wert zu schreiben, wieder aus dem Speicher. Allerdings habe ich einige seltsame Leistungsergebnisse.

In meinem ersten Schema ich die _mm_set_epi8 intrinsische verwenden meine __m128i wie folgt festzulegen:

    const __m128i packedSamples = _mm_set_epi8( sample15,   sample14,   sample13,   sample12,   sample11,   sample10,   sample9,    sample8,
                                                sample7,    sample6,    sample5,    sample4,    sample3,    sample2,    sample1,    sample0 );

Im Grunde lasse ich sie alle an den Compiler entscheiden, wie es zu optimieren beste Leistung zu geben. Dies gibt schlechteste Leistung. MY Testläufe in ~ 0,195 Sekunden.

Zweitens habe ich versucht, unter Verwendung von 4 _mm_set_epi32 Anweisungen nach unten zusammenfassen und dann der Verpackung nach unten:

    const __m128i samples0      = _mm_set_epi32( sample3, sample2, sample1, sample0 );
    const __m128i samples1      = _mm_set_epi32( sample7, sample6, sample5, sample4 );
    const __m128i samples2      = _mm_set_epi32( sample11, sample10, sample9, sample8 );
    const __m128i samples3      = _mm_set_epi32( sample15, sample14, sample13, sample12 );

    const __m128i packedSamples0    = _mm_packs_epi32( samples0, samples1 );
    const __m128i packedSamples1    = _mm_packs_epi32( samples2, samples3 );
    const __m128i packedSamples     = _mm_packus_epi16( packedSamples0, packedSamples1 );

Das tut Leistung etwas verbessern. Mein Test läuft nun in ~ 0,15 Sekunden. Erscheint unlogisch, dass die Leistung, indem Sie diese verbessern würde, wie ich nehme an, das ist genau das, was _mm_set_epi8 sowieso tut ...

Mein letzter Versuch war ein Stück Code zu verwenden, die ich von zu machen vier CCs auf die altmodische Weise (mit Verschiebungen und ors) habe und diese dann in einem __m128i mit einem einzigen _mm_set_epi32 setzen.

    const GCui32 samples0       = MakeFourCC( sample0, sample1, sample2, sample3 );
    const GCui32 samples1       = MakeFourCC( sample4, sample5, sample6, sample7 );
    const GCui32 samples2       = MakeFourCC( sample8, sample9, sample10, sample11 );
    const GCui32 samples3       = MakeFourCC( sample12, sample13, sample14, sample15 );
    const __m128i packedSamples = _mm_set_epi32( samples3, samples2, samples1, samples0 );

Das gibt sogar eine bessere Leistung. Unter ~ 0,135 Sekunden meinen Test auszuführen. Ich fange wirklich verwirrt.

Also habe ich versucht, ein einfaches Lese Byteschreib Byte-System und das ist immer so etwas schneller als selbst die letzte Methode.

Also, was ist hier los? Das alles erscheint unlogisch zu mir.

Ich habe die Idee ausgegangen, dass die Verzögerungen auf dem _mm_stream_ps werden auftreten, weil ich Daten bin zu schnell liefern, aber dann würde ich genau die gleichen Ergebnisse raus, was auch immer ich tue. Ist es möglich, dass die ersten zwei Methoden bedeuten, dass die 16 Lasten nicht durch die Schleife verteilt bekommen können zu Latenz verbergen? Wenn ja, warum ist das? Sicherlich kann eine intrinsische der Compiler machen Optimierungen wie und wo es gefällt .. Ich dachte, dass der ganze Punkt war ... auch sicherlich die Durchführung 16 liest und schreibt 16 wird viel langsamer als 16 liest und 1 Schreib mit einem Bündel von SSE Jonglieren Anweisungen ... Schließlich ist es das liest und schreibt, dass die langsame Bit!

sind

Jeder mit irgendwelchen Ideen, was los wird sehr geschätzt! : D

Edit: Weiter im Kommentar unten I Vorladen die Bytes als Konstanten und changedit dazu angehalten:

    const __m128i samples0      = _mm_set_epi32( *(pSamples + channelStep3), *(pSamples + channelStep2), *(pSamples + channelStep1), *(pSamples + channelStep0) );
    pSamples    += channelStep4;
    const __m128i samples1      = _mm_set_epi32( *(pSamples + channelStep3), *(pSamples + channelStep2), *(pSamples + channelStep1), *(pSamples + channelStep0) );
    pSamples    += channelStep4;
    const __m128i samples2      = _mm_set_epi32( *(pSamples + channelStep3), *(pSamples + channelStep2), *(pSamples + channelStep1), *(pSamples + channelStep0) );
    pSamples    += channelStep4;
    const __m128i samples3      = _mm_set_epi32( *(pSamples + channelStep3), *(pSamples + channelStep2), *(pSamples + channelStep1), *(pSamples + channelStep0) );
    pSamples    += channelStep4;

    const __m128i packedSamples0    = _mm_packs_epi32( samples0, samples1 );
    const __m128i packedSamples1    = _mm_packs_epi32( samples2, samples3 );
    const __m128i packedSamples     = _mm_packus_epi16( packedSamples0, packedSamples1 );

und diese verbesserte Leistung ~ 0,143 Sekunden. Sitll nicht so gut wie die gerade C-Implementierung ...

Bearbeiten Nochmal: Die beste Leistung erhalte ich so weit ist

    // Load the samples.
    const GCui8 sample0     = *(pSamples + channelStep0);
    const GCui8 sample1     = *(pSamples + channelStep1);
    const GCui8 sample2     = *(pSamples + channelStep2);
    const GCui8 sample3     = *(pSamples + channelStep3);

    const GCui32 samples0   = Build32( sample0, sample1, sample2, sample3 );
    pSamples += channelStep4;

    const GCui8 sample4     = *(pSamples + channelStep0);
    const GCui8 sample5     = *(pSamples + channelStep1);
    const GCui8 sample6     = *(pSamples + channelStep2);
    const GCui8 sample7     = *(pSamples + channelStep3);

    const GCui32 samples1   = Build32( sample4, sample5, sample6, sample7 );
    pSamples += channelStep4;

    // Load the samples.
    const GCui8 sample8     = *(pSamples + channelStep0);
    const GCui8 sample9     = *(pSamples + channelStep1);
    const GCui8 sample10    = *(pSamples + channelStep2);
    const GCui8 sample11    = *(pSamples + channelStep3);

    const GCui32 samples2       = Build32( sample8, sample9, sample10, sample11 );
    pSamples += channelStep4;

    const GCui8 sample12    = *(pSamples + channelStep0);
    const GCui8 sample13    = *(pSamples + channelStep1);
    const GCui8 sample14    = *(pSamples + channelStep2);
    const GCui8 sample15    = *(pSamples + channelStep3);

    const GCui32 samples3   = Build32( sample12, sample13, sample14, sample15 );
    pSamples += channelStep4;

    const __m128i packedSamples = _mm_set_epi32( samples3, samples2, samples1, samples0 );

    _mm_stream_ps( pWrite + 0,  *(__m128*)&packedSamples ); 

Das gibt mir die Verarbeitung in ~ 0,095 Sekunden, die wesentlich besser ist. Ich scheinen nicht in der Lage zu sein, schließen allerdings mit SSE zu bekommen ... Ich bin durch das immer noch verwirrt, aber .. ho hum.

War es hilfreich?

Lösung

Vielleicht ist der Compiler versucht, die intrinsische in die Register auf einmal alle Argumente zu setzen. Sie wollen nicht auf einmal, dass viele Variablen zugreifen, ohne sie zu organisieren.

Anstatt eine separate Kennung für jede Probe zu erklären, versuchen, sie in eine char[16] setzen. Der Compiler wird die 16 Werte in die Register fördern, wie es für richtig hält, solange man nicht im Array die Adresse von etwas nehmen. Sie können einen __aligned__-Tag (oder was auch immer VC ++ verwendet) hinzuzufügen, und vielleicht die intrinsischen ganz zu vermeiden. Andernfalls sollte die intrinsischen mit ( sample[15], sample[14], sample[13] … sample[0] ) Aufruf den Job des Compiler erleichtern oder zumindest nicht schaden.


Edit: Ich bin mir ziemlich sicher, dass Sie ein Register spill kämpfen, aber dieser Vorschlag wird wahrscheinlich nur speichern die Bytes einzeln, was nicht ist, was Sie wollen. Ich denke, mein Rat ist Ihren letzten Versuch (mit MakeFourCC) mit den Leseoperationen verschachteln, um sicherzustellen, dass es richtig und ohne Rundfahrten auf den Stapel geplant ist. Natürlich ist die Inspektion des Objektcodes der beste Weg, um sicherzustellen.

Im Wesentlichen ist Streaming von Daten in die Registerdatei und Streaming es dann wieder aus. Sie wollen es nicht zu überlasten, bevor es an der Zeit, die Daten zu spülen.

Andere Tipps

VS ist notorisch schlecht intrinsics zu optimieren. Besonders Verschieben von Daten von und zu SSE-Registern. Die Spezifika selbst sind ziemlich gut aber verwendet ....

Was Sie sehen, ist, dass es versucht, die SSE-Register mit diesem Monster zu füllen:

00AA100C  movzx       ecx,byte ptr [esp+0Fh]  
00AA1011  movzx       edx,byte ptr [esp+0Fh]  
00AA1016  movzx       eax,byte ptr [esp+0Fh]  
00AA101B  movd        xmm0,eax  
00AA101F  movzx       eax,byte ptr [esp+0Fh]  
00AA1024  movd        xmm2,edx  
00AA1028  movzx       edx,byte ptr [esp+0Fh]  
00AA102D  movd        xmm1,ecx  
00AA1031  movzx       ecx,byte ptr [esp+0Fh]  
00AA1036  movd        xmm4,ecx  
00AA103A  movzx       ecx,byte ptr [esp+0Fh]  
00AA103F  movd        xmm5,edx  
00AA1043  movzx       edx,byte ptr [esp+0Fh]  
00AA1048  movd        xmm3,eax  
00AA104C  movzx       eax,byte ptr [esp+0Fh]  
00AA1051  movdqa      xmmword ptr [esp+60h],xmm0  
00AA1057  movd        xmm0,edx  
00AA105B  movzx       edx,byte ptr [esp+0Fh]  
00AA1060  movd        xmm6,eax  
00AA1064  movzx       eax,byte ptr [esp+0Fh]  
00AA1069  movd        xmm7,ecx  
00AA106D  movzx       ecx,byte ptr [esp+0Fh]  
00AA1072  movdqa      xmmword ptr [esp+20h],xmm4  
00AA1078  movdqa      xmmword ptr [esp+80h],xmm0  
00AA1081  movd        xmm4,ecx  
00AA1085  movzx       ecx,byte ptr [esp+0Fh]  
00AA108A  movdqa      xmmword ptr [esp+70h],xmm2  
00AA1090  movd        xmm0,eax  
00AA1094  movzx       eax,byte ptr [esp+0Fh]  
00AA1099  movdqa      xmmword ptr [esp+10h],xmm4  
00AA109F  movdqa      xmmword ptr [esp+50h],xmm6  
00AA10A5  movd        xmm2,edx  
00AA10A9  movzx       edx,byte ptr [esp+0Fh]  
00AA10AE  movd        xmm4,eax  
00AA10B2  movzx       eax,byte ptr [esp+0Fh]  
00AA10B7  movd        xmm6,edx  
00AA10BB  punpcklbw   xmm0,xmm1  
00AA10BF  punpcklbw   xmm2,xmm3  
00AA10C3  movdqa      xmm3,xmmword ptr [esp+80h]  
00AA10CC  movdqa      xmmword ptr [esp+40h],xmm4  
00AA10D2  movd        xmm4,ecx  
00AA10D6  movdqa      xmmword ptr [esp+30h],xmm6  
00AA10DC  movdqa      xmm1,xmmword ptr [esp+30h]  
00AA10E2  movd        xmm6,eax  
00AA10E6  punpcklbw   xmm4,xmm5  
00AA10EA  punpcklbw   xmm4,xmm0  
00AA10EE  movdqa      xmm0,xmmword ptr [esp+50h]  
00AA10F4  punpcklbw   xmm1,xmm0  
00AA10F8  movdqa      xmm0,xmmword ptr [esp+70h]  
00AA10FE  punpcklbw   xmm6,xmm7  
00AA1102  punpcklbw   xmm6,xmm2  
00AA1106  movdqa      xmm2,xmmword ptr [esp+10h]  
00AA110C  punpcklbw   xmm2,xmm0  
00AA1110  movdqa      xmm0,xmmword ptr [esp+20h]  
00AA1116  punpcklbw   xmm1,xmm2  
00AA111A  movdqa      xmm2,xmmword ptr [esp+40h]  
00AA1120  punpcklbw   xmm2,xmm0  
00AA1124  movdqa      xmm0,xmmword ptr [esp+60h]  
00AA112A  punpcklbw   xmm3,xmm0  
00AA112E  punpcklbw   xmm2,xmm3  
00AA1132  punpcklbw   xmm6,xmm4  
00AA1136  punpcklbw   xmm1,xmm2  
00AA113A  punpcklbw   xmm6,xmm1  

Das funktioniert viel besser und (soll) leicht schneller:

__declspec(align(16)) BYTE arr[16] = { sample15, sample14, sample13, sample12, sample11, sample10, sample9, sample8, sample7, sample6, sample5, sample4, sample3, sample2, sample1, sample0 };

__m128i packedSamples = _mm_load_si128( (__m128i*)arr );

meinen eigenen Prüfstands Körperbau:

void    f()
{
    const int steps = 1000000;
    BYTE* pDest = new BYTE[steps*16+16];
    pDest += 16 - ((ULONG_PTR)pDest % 16);
    BYTE* pSrc = new BYTE[steps*16*16];

    const int channelStep0 = 0;
    const int channelStep1 = 1;
    const int channelStep2 = 2;
    const int channelStep3 = 3;
    const int channelStep4 = 16;

    __int64 freq;
    QueryPerformanceFrequency( (LARGE_INTEGER*)&freq );
    __int64 start = 0, end;
    QueryPerformanceCounter( (LARGE_INTEGER*)&start );

    for( int step = 0; step < steps; ++step )
    {
        __declspec(align(16)) BYTE arr[16];
        for( int j = 0; j < 4; ++j )
        {
            //for( int i = 0; i < 4; ++i )
            {
                arr[0+j*4] = *(pSrc + channelStep0);
                arr[1+j*4] = *(pSrc + channelStep1);
                arr[2+j*4] = *(pSrc + channelStep2);
                arr[3+j*4] = *(pSrc + channelStep3);
            }
            pSrc += channelStep4;
        }

#if test1
// test 1 with C
        for( int i = 0; i < 16; ++i )
        {
            *(pDest + step * 16 + i) = arr[i];
        }
#else
// test 2 with SSE load/store    
        __m128i packedSamples = _mm_load_si128( (__m128i*)arr );
        _mm_stream_si128( ((__m128i*)pDest) + step, packedSamples );
#endif
    }

    QueryPerformanceCounter( (LARGE_INTEGER*)&end );

    printf( "%I64d", (end - start) * 1000 / freq );

}

Für mich Test 2 ist schneller als Test 1.

Habe ich etwas falsch gemacht? Ist dies nicht der Code, den Sie verwenden? Was ich vermisse? Ist das nur für mich?

intrinsics Umbrüche Compiler-Optimierungen verwenden!

Der ganze Sinn der intrinsischen Funktionen OP-Codes einzufügen der Compiler nicht etwa in den Strom der Compiler von Opcodes kennt weiß über und erzeugt hat. Es sei denn, der Compiler einige Meta-Daten über den Opcode gegeben wird und wie sie die Register und Speicher betreffen, kann der Compiler nicht davon ausgehen, dass alle Daten nach dem Ausführen der intrinsischen erhalten bleiben. Das tut wirklich weh die Optimierung Teil des Compilers -. Es keine Anweisungen um die intrinsische neu anordnen kann, kann sie nicht davon ausgehen, Register sind nicht betroffen und so weiter

Ich denke, der beste Weg, dies zu optimieren, um das größere Bild zu sehen ist - Sie können den gesamten Prozess betrachten müssen die Quelldaten aus der Lektüre der endgültigen Ausgabe zu schreiben. Micro-Optimierungen selten große Ergebnisse geben, wenn Sie etwas wirklich schlecht tun mit zu beginnen.

Vielleicht, wenn Sie ausführlich das erforderlich Ein- und Ausgang jemand hier könnte eine optimale Methode vorschlagen, sie zu handhaben.

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top