J'effectue une lecture dispersée de données de 8 bits à partir d'un fichier (De-Interleaving un fichier d'onde de 64 canaux). Je les combine ensuite pour former un seul flux d'octets. Le problème que j'ai est avec ma reconstruction des données à écrire. Fondamentalement, je lis 16 octets et ensuite les construire en une seule variable __m128i, puis en utilisant _mm_stream_ps pour écrire la valeur en mémoire. Cependant, j'ai des résultats de performance bizarres.Bizarre d'optimisation intrinsèque VC++ SSE
Dans mon premier schéma j'utilise la _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);
Fondamentalement, je laisse le tout au compilateur de décider comment l'optimiser pour obtenir la meilleure performance. Cela donne la pire performance. MON test s'exécute en ~ 0.195 secondes.
Deuxièmement, j'ai 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éliore quelque peu les performances. Mon test fonctionne maintenant dans ~ 0.15 secondes. Il semble contre-intuitif que les performances s'améliorent en faisant cela car je suppose que c'est exactement ce que _mm_set_epi8 fait de toute façon ...
Ma dernière tentative a été d'utiliser un peu de code que j'ai pour faire quatre CCs à l'ancienne (avec des décalages et des orcs) et ensuite 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 à être confus. J'ai donc essayé un simple octet de lecture octet octet système et c'est toujours un peu plus rapide que la dernière méthode.
Alors, que se passe-t-il? Tout cela me semble contre-intuitif. J'ai considéré l'idée que les retards se produisaient sur le _mm_stream_ps parce que je fournissais des données trop rapidement mais alors j'obtiendrais exactement les mêmes résultats dehors que je fais. Est-il possible que les 2 premières méthodes signifient que les 16 charges ne peuvent pas être distribuées à travers la boucle pour cacher la latence? Si oui, pourquoi est-ce? Sûrement un intrinsèque permet au compilateur de faire des optimisations au fur et à mesure qu'il le souhaite .. je pensais que c'était tout le point ... Aussi sûrement effectuer 16 lectures et 16 écritures sera beaucoup plus lent que 16 lectures et 1 écrire avec un tas de jonglerie SSE instructions ... Après tout ce sont les lectures et les écritures qui sont le bit lent!
Toute personne ayant des idées 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 droite C ...
Modifier Encore une fois: La meilleure performance que je reçois est donc loin
// 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 si ... Je suis toujours confus par cela, mais ..ho hum.
Jetez un oeil à l'assembleur produit par le compilateur. Peut-être que cela donne un aperçu du problème. –
Hmm fair point ... Je devrais vérifier que ... le compilateur génère du code VRAIMENT bizarre ... Si seulement il y avait un intrinsèque pour les lectures dispersées :( – Goz
Vous avez doublé les performances par rapport à l'implémentation intrinsèque naïve! mauvais et vous utilisez SSE, pour assembler les morceaux de 4 octets en 16 octets et enfin le stocker.Après le déversement, le problème est que vous n'avez pas beaucoup de travail pour SSE à faire, et vous devez éviter de trop bouger inss des registres généraux aux registres SSE SSE brillerait vraiment si vous lisiez un flux continu, mais ce n'est pas le cas – Potatoswatter