Domanda

sto eseguendo una lettura sparsa dei dati a 8 bit da un file (De-Interleaving un file wave a 64 canali). Sto combinando poi loro di essere un unico flusso di byte. Il problema che sto avendo è con la mia ricostruzione dei dati di scrivere.

Fondamentalmente sto leggendo in 16 byte e quindi la loro costruzione in una singola variabile __m128i e quindi utilizzando _mm_stream_ps scrivere il valore indietro alla memoria. Tuttavia ho qualche risultato prestazioni dispari.

Nel mio primo schema io uso il _mm_set_epi8 intrinseca per impostare il mio __m128i come segue:

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

In sostanza Lascio tutto fino al compilatore di decidere come ottimizzare per dargli le migliori prestazioni. Questo dà peggiore performance. Il mio test viene eseguito in ~ 0.195 secondi.

In secondo luogo ho cercato di fondere verso il basso utilizzando 4 _mm_set_epi32 istruzioni e poi li imballaggio verso il basso:

    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 );

Questo fa migliorare le prestazioni un po '. La mia prova ora gira in ~ 0,15 secondi. Sembra contro-intuitivo che le prestazioni migliorerebbe facendo questo come Suppongo che questo è esattamente ciò che sta facendo _mm_set_epi8 comunque ...

Il mio ultimo tentativo è stato quello di usare un po 'di codice che ho da fare quattro CC alla vecchia maniera (con turni e ORS) e poi metterli in un __m128i utilizzando un unico _mm_set_epi32.

    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 );

In questo modo le prestazioni ancora migliori. Prendendo ~ 0,135 secondi per eseguire la mia prova. Sto davvero iniziando a confondersi.

Così ho provato un semplice sistema di byte di lettura e scrittura di byte e che è sempre così leggermente più veloce, anche l'ultimo metodo.

Allora, cosa sta succedendo? Questo sembra tutto contro-intuitivo per me.

ho considerato l'idea che i ritardi sono verificano sulle _mm_stream_ps perché sto fornendo dati troppo rapidamente, ma poi avrei per ottenere esattamente gli stessi risultati fuori tutto quello che faccio. E 'possibile che i primi 2 metodi fanno sì che i 16 carichi non possano ottenere distribuito attraverso il ciclo di nascondere la latenza? Se è così perché è questo? Sicuramente un intrinseco permette al compilatore di fare ottimizzazioni come e dove vuole .. Ho pensato che era il punto ... Anche sicuramente eseguendo 16 letture e scritture 16 sarà molto più lento di 16 letture e scrittura 1 con un gruppo di SSE giocoleria istruzioni ... Dopo tutta la sua la lettura e scrittura che sono il po 'lento!

Chiunque con tutte le idee che cosa sta succedendo sarà molto apprezzato! : D

Edit: A seguito della commento qui sotto ho smesso di pre-caricamento dei byte come costanti e changedit a questo:

    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 );

e questo miglioramento delle prestazioni per ~ 0,143 secondi. Sitll non buono come l'attuazione diritta C ...

Modifica Again: La migliore performance sto ottenendo finora è

    // 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 ); 

Questo mi dà la lavorazione in ~ 0,095 secondi che è notevolmente migliore. Io non sembrano essere in grado di avvicinarsi con SSE però ... Sono ancora confuso da questo, ma .. indifferente.

È stato utile?

Soluzione

Forse il compilatore sta cercando di mettere tutti gli argomenti alla intrinseca in registri in una sola volta. Non si desidera accedere che molte variabili in una sola volta senza di loro organizzazione.

Piuttosto che dichiarare un identificatore separato per ogni campione, provare a metterli in un char[16]. Il compilatore promuoverà i 16 valori ai registri come meglio ritiene opportuno, a patto che non si prende l'indirizzo di qualsiasi cosa all'interno della matrice. È possibile aggiungere un tag __aligned__ (o qualsiasi altra cosa usi VC ++) e forse evitare l'intrinseca del tutto. In caso contrario, chiamando il intrinseco con ( sample[15], sample[14], sample[13] … sample[0] ) dovrebbe rendere il lavoro del compilatore più facile o almeno non nuocere.


Modifica sono abbastanza sicuro che si sta combattendo una fuoriuscita registro, ma che suggerimento sarà probabilmente solo memorizzare i byte singolarmente, che non è quello che si desidera. Penso che il mio consiglio è di alternare il tuo ultimo tentativo (usando MakeFourCC) con le operazioni di lettura, per assicurarsi che sia pianificato in modo corretto e senza andata e ritorno allo stack. Naturalmente, l'ispezione del codice oggetto è il modo migliore per garantire che.

In sostanza, si sta streaming di dati nel file di registro e quindi lo streaming di nuovo fuori. Non si vuole sovraccaricare prima è il momento di svuotare i dati.

Altri suggerimenti

VS è notoriamente male ad ottimizzare intrinseche. Soprattutto trasferire dati da e ai registri SSE. I intrinseci si sono utilizzati abbastanza bene però ....

Quello che si vede è che si sta cercando di riempire lo SSE registrare con questo mostro:

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  

Questo funziona molto meglio e (dovrebbe) facilmente essere più veloce:

__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 );

costruire il mio banco di prova:

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 );

}

Per me prova 2 è più veloce di prova 1.

Ho fatto qualcosa di sbagliato? Non è questo il codice che si sta utilizzando? Cosa mi manca? E 'questo solo per me?

Utilizzando ottimizzazioni intrinseche pause del compilatore!

Lo scopo delle funzioni intrinseche inserirà opcode compilatore non conosce nel flusso di codici operativi compilatore sa circa e ha generato. A meno che il compilatore è dato alcuni metadati sul codice operativo e come influenza i registri e la memoria, il compilatore non può assumere che i dati è conservato dopo l'esecuzione del intrinseco. Questo fa male davvero la parte ottimizzazione del compilatore -. Non può riordinare istruzioni intorno al intrinseca, non può assumere registri sono inalterata e così via

Credo che il modo migliore per ottimizzare questo è quello di guardare il quadro più ampio - è necessario considerare l'intero processo, dalla lettura dei dati di origine per scrivere l'output finale. ottimizzazioni Micro raramente danno grandi risultati, a meno che non si sta facendo qualcosa di veramente male per cominciare.

Forse, se si dettaglio l'input e l'output desiderato qualcuno qui potrebbe suggerire un metodo ottimale per gestire la cosa.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top