Question

Je l'exécution d'une lecture dispersée de données de 8 bits à partir d'un fichier (désentrelacement un fichier d'onde de canal 64). Je suis alors les combiner pour être un seul flux d'octets. Le problème que je vais avoir est avec ma re-construction des données à écrire.

En fait, je lis dans 16 octets, puis les intégrer dans une seule variable __m128i puis en utilisant _mm_stream_ps pour écrire la valeur de retour vers la mémoire. Cependant, j'ai des résultats de performance impairs.

Dans mon premier schéma j'utilise le _mm_set_epi8 intrinsèque pour définir mon __m128i comme suit:

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

En fait, je laisse le tout au compilateur de décider comment l'optimiser pour obtenir la meilleure performance. Cela donne plus Mauvaise performance. Mon test fonctionne en 0.195 secondes ~.

Deuxièmement, j'essayé de fusionner vers le bas en utilisant 4 _mm_set_epi32 instructions, puis les tassant:

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

Cela n'améliorer les performances un peu. Mon test fonctionne maintenant dans ~ 0,15 secondes. Il semble contre-intuitif que la performance améliorerait en faisant cela que je suppose que c'est exactement ce que _mm_set_epi8 est en train de faire de toute façon ...

Ma dernière tentative était d'utiliser un peu de code que j'ai de faire quatre CCs l'ancienne façon fashioned (avec des changements et ORS), puis les mettre dans un __m128i en utilisant un seul _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 );

Cela donne des résultats encore meilleurs. Prendre ~ 0,135 secondes pour exécuter mon test. Je commence vraiment à se confondre.

J'ai donc essayé un simple système d'écriture d'octets d'octets de lecture et qui est toujours aussi un peu plus vite que même la dernière méthode.

Alors qu'est-ce qui se passe? Cela me semble tout contre-intuitif.

Je l'ai considéré l'idée que les retards sont les INTERVENUES _mm_stream_ps parce que je fournir des données trop rapidement, mais alors je pour obtenir exactement les mêmes résultats sur tout ce que je fais. Est-il possible que les 2 premières méthodes signifient que les 16 charges peuvent pas distribuées à travers la boucle pour cacher la latence? Si oui, pourquoi est-ce? Certes, un intrinsèque permet au compilateur de faire Optimisations au fur et où il veut .. Je pensais que tout était le point ... Aussi EFFECTUER sûrement 16 lectures et 16 écritures seront beaucoup plus lent que 16 lectures et 1 écriture avec un groupe de jonglage SSE instructions ... Après tout c'est le lit et écrit qui sont le peu lent!

Toute personne ayant des idées sur ce qui se passe sera très appréciée! : D

Edit: Suite au commentaire ci-dessous je me suis arrêté avant le chargement des octets comme des constantes et changedit à ceci:

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

et cette amélioration de la performance à ~ 0,143 secondes. Tujoruos pas aussi bon que la mise en œuvre du droit C ...

Modifier Encore une fois: La meilleure performance que je reçois est à ce jour

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

Cela me donne le traitement en ~ 0,095 secondes, ce qui est beaucoup mieux. Je ne semble pas être en mesure de se rapprocher avec SSE mais ... Je suis encore confus par cela, mais .. ho hum.

Était-ce utile?

La solution

Peut-être que le compilateur essaie de mettre tous les arguments à la valeur intrinsèque dans des registres à la fois. Vous ne voulez pas accéder que de nombreuses variables à la fois sans les organiser.

Plutôt que de déclarer un identifiant distinct pour chaque échantillon, essayer de les mettre dans un char[16]. Le compilateur favorisera les 16 valeurs de registres comme il l'entend, aussi longtemps que vous ne prenez pas l'adresse de quoi que ce soit dans le tableau. Vous pouvez ajouter une balise __aligned__ (ou tout autre utilisation VC ++) et peut-être éviter le tout à fait intrinsèque. Dans le cas contraire, appeler la valeur intrinsèque avec ( sample[15], sample[14], sample[13] … sample[0] ) devrait rendre le travail du compilateur plus facile ou du moins ne pas nuire.


Modifier Je suis sûr que vous vous battez contre un déversement de registre, mais cette suggestion sera probablement stocker les octets individuellement, ce qui est pas ce que vous voulez. Je pense que mon conseil est de votre dernière tentative entrelacer (en utilisant MakeFourCC) avec les opérations de lecture, pour vous assurer qu'il est prévu correctement et sans allers-retours à la pile. Bien sûr, l'inspection du code objet est la meilleure façon de faire en sorte que.

Pour l'essentiel, vous diffusez des données dans le fichier de registre et le streaming en arrière sur. Vous ne voulez pas surcharger avant qu'il est temps de vider les données.

Autres conseils

VS est notoirement mauvaise à optimiser intrinsics. En particulier, le déplacement des données depuis et vers les registres SSE. Les intrinsics lui-même sont utilisés assez bien mais ....

Ce que vous voyez est qu'il tente de remplir le registre SSE avec ce monstre:

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  

Cela fonctionne beaucoup mieux et (devrait) être facilement plus rapidement:

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

Créer mon banc d'essai:

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

}

Pour moi test 2 est plus rapide que le test 1.

Est-ce que je fais quelque chose de mal? Est-ce pas le code que vous utilisez? Que dois-je manquer? Est-ce juste pour moi?

Utilisation Optimisations du compilateur de pauses intrinsèques!

Le point de l'ensemble des fonctions intrinsèques est d'insérer opcodes le compilateur ne connaît pas dans le flux de opcodes le compilateur ne sait au sujet et a généré. À moins que le compilateur est donné des méta-données sur l'opcode et comment elle affecte les registres et la mémoire, le compilateur ne peut supposer que toutes les données sont conservées après l'exécution de la valeur intrinsèque. Cela fait vraiment mal la partie optimisation du compilateur -. Il ne peut pas modifier l'ordre des instructions autour de la valeur intrinsèque, il ne peut pas assumer les registres ne sont pas affectés et ainsi de suite

Je pense que la meilleure façon d'optimiser est de regarder le tableau d'ensemble - vous devez considérer le processus de lecture des données de source pour écrire la sortie finale. Micro donnent rarement de grands Optimisations résultats, à moins que vous faites quelque chose de vraiment mal pour commencer.

Peut-être, si vous détaillez la personne d'entrée et de sortie requis ici pourrait suggérer une méthode optimale pour le manipuler.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top