Dans un suivi de certaines questions précédentes sur la conversion de RVB en RGBA, et Argb à BGR, je voudrais accélérer une conversion La routine à être vectorisée est la suivante ... P> gcc -O2 main.c
4 Réponses :
Je n'ai pas la compréhension complète de ce que vous demandez, et j'attends une réponse appropriée à votre question. Entre-temps, j'ai mis en place une implémentation approximative de 8 à 10% plus rapide en moyenne. Je suis en cours d'exécution Win7 64bit, en utilisant VS2010, compilant avec C ++ pour la libération avec l'option rapide.
#pragma pack(push, 1) struct RGB { unsigned char r, g, b; }; struct BGRA { unsigned char b, g, r, a; }; #pragma pack(pop) void RGB8ToBGRX8(int width, const void* in, void* out) { const RGB* src = (const RGB*)in; BGRA* dst = (BGRA*)out; do { dst->r = src->r; dst->g = src->g; dst->b = src->b; dst->a = 0xFF; src++; dst++; } while (--width); }
Pas d'inquiétude Jack! Si vous pouviez clarifier quelle pièce vous ne pouvez pas comprendre, je peux essayer de préciser. :)
Qu'entendez-vous d'utiliser SSE? Je pense que cela signifie instructeur au compilateur d'utiliser des techniques d'optimisation spécifiques, et si c'est le cas peut-être que ce n'est pas la peine de modifier le code à la main. Vous dites également que vous souhaitez utiliser intrinsique, que voulez-vous dire? Cependant, j'ai une bonne compréhension de la parallélisation.
Oh. Je parlais de l'intris de vectorisation de l'utilisation de SSE2 / 3 ou de SSsee. Surtout les opérations de rembourrage / masquage, comme j'ai vu des solutions élégantes avec d'autres conversions d'image. Maintenant, je sais que GCC4.x a plusieurs drapeaux de compilation qui aident ici, mais je suis incertain de quoi et / ou si c'est mieux. Peut-être que votre expertise serait utile ici.
Ok je suis plus proche de la compréhension. Non Désolé, je ne suis pas un expert avec GCC.
Il s'agit d'un exemple d'utilisation de l'intrigue SSSE3 pour effectuer l'opération demandée. Les pointeurs d'entrée et de sortie doivent être alignés de 16 octets et fonctionnent sur un bloc de 16 pixels à la fois.
#include <tmmintrin.h> /* in and out must be 16-byte aligned */ void rgb_to_bgrx_sse(unsigned w, const void *in, void *out) { const __m128i *in_vec = in; __m128i *out_vec = out; w /= 16; while (w-- > 0) { /* 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 * in_vec[0] Ra Ga Ba Rb Gb Bb Rc Gc Bc Rd Gd Bd Re Ge Be Rf * in_vec[1] Gf Bf Rg Gg Bg Rh Gh Bh Ri Gi Bi Rj Gj Bj Rk Gk * in_vec[2] Bk Rl Gl Bl Rm Gm Bm Rn Gn Bn Ro Go Bo Rp Gp Bp */ __m128i in1, in2, in3; __m128i out; in1 = in_vec[0]; out = _mm_shuffle_epi8(in1, _mm_set_epi8(0xff, 9, 10, 11, 0xff, 6, 7, 8, 0xff, 3, 4, 5, 0xff, 0, 1, 2)); out = _mm_or_si128(out, _mm_set_epi8(0xff, 0, 0, 0, 0xff, 0, 0, 0, 0xff, 0, 0, 0, 0xff, 0, 0, 0)); out_vec[0] = out; in2 = in_vec[1]; in1 = _mm_and_si128(in1, _mm_set_epi8(0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0, 0, 0, 0, 0, 0, 0, 0)); out = _mm_and_si128(in2, _mm_set_epi8(0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff)); out = _mm_or_si128(out, in1); out = _mm_shuffle_epi8(out, _mm_set_epi8(0xff, 5, 6, 7, 0xff, 2, 3, 4, 0xff, 15, 0, 1, 0xff, 12, 13, 14)); out = _mm_or_si128(out, _mm_set_epi8(0xff, 0, 0, 0, 0xff, 0, 0, 0, 0xff, 0, 0, 0, 0xff, 0, 0, 0)); out_vec[1] = out; in3 = in_vec[2]; in_vec += 3; in2 = _mm_and_si128(in2, _mm_set_epi8(0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0, 0, 0, 0, 0, 0, 0, 0)); out = _mm_and_si128(in3, _mm_set_epi8(0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff)); out = _mm_or_si128(out, in2); out = _mm_shuffle_epi8(out, _mm_set_epi8(0xff, 1, 2, 3, 0xff, 14, 15, 0, 0xff, 11, 12, 13, 0xff, 8, 9, 10)); out = _mm_or_si128(out, _mm_set_epi8(0xff, 0, 0, 0, 0xff, 0, 0, 0, 0xff, 0, 0, 0, 0xff, 0, 0, 0)); out_vec[2] = out; out = _mm_shuffle_epi8(in3, _mm_set_epi8(0xff, 13, 14, 15, 0xff, 10, 11, 12, 0xff, 7, 8, 9, 0xff, 4, 5, 6)); out = _mm_or_si128(out, _mm_set_epi8(0xff, 0, 0, 0, 0xff, 0, 0, 0, 0xff, 0, 0, 0, 0xff, 0, 0, 0)); out_vec[3] = out; out_vec += 4; } }
Même GCC8.2 -O3 code> n'oblise pas la version de l'OP dans une charge de 4 octets. ICC et CLANG
-O3 CODE> DIRELLELT MAIS NE FAISONNE NE FAISON TOUJOURS NE FAIRE NECT NECT PAS QUELLE BYTE-CHARGES + ou godbolt.org/z/ei9c_d . Sur un processeur SANDYBRIDGE-FAMILY, la version de GCC Il fonctionnera au mieux 4 octets stockés par 3 cycles d'horloge, ou moins si vous concurrencez une hyperthread, goulot d'étranglement sur le front-end à 4 Uops par horloge. C'est des ordures. Difficile d'imaginer un cas où cette version
pshufb code> ne serait pas au moins 3 fois plus rapide, et plus facilement en fonction de la bande passante mémoire.
Hmm, on dirait que certaines optimisations manquées, cependant. Utilisez palignr code> /
_mm_alignr_epi8 code> pour obtenir quatre fenêtres de 9 octets à partir de 3 charges alignées, au lieu de et / et / ou de fusionner. Ou utilisez
MOVSD code> ou
punpcklqdq code> pour fusionner des moitiés hautes / bas, ou combinez des moitiés à faible bas. Ou surtout sur Haswell et plus tard (1 shuffing par horloge), faites quatre charges non alignées. Nehalem / K10 et plus tard ont des charges non alignées efficaces. (Mais la page se divise toujours aspirer jusqu'à la skylake.)
@Petercordes: Oui, vous avez raison - il est possible de Tweak le code scalaire pour obtenir des charges de 4 octets une> mais cela n'a toujours pas l'air vite. Je ne suis pas sûr de la bande passante de la mémoire que je comparais à 7 ans, 7 ans est longue. L'optimisation code> palignr code> est bonne, je pourrais donner cela un essai.
Oh, j'ai oublié que cela inversait l'ordre d'octet à BGRA, aussi plutôt que simplement SSE2 Convertir SSE2 Convertir RGB Emballé en pixels RGBA (ajoutez un octet de 4ème 0xFF après tous les 3 octets) . Utilisez une fonction d'inversion de Endian comme __ intégré_bswap32 (dans) | 0xFF000000 CODE> Pour obtenir
MOV code> +
BSWAP code> +
ou code> +
MOV code>. (Mais c'est toujours 4 Uops Total ne comptant pas de surcharge de boucle pour
Pointants + = 3 * déroulement code> et
+ = 4 * dérouler code>, nous ne pouvons donc approcher que 1 DWord Store par horloge Avec un énorme déroulement) ou sur Atom / Silvermont (mais pas haswell),
movbe code> peut enregistrer un UOP.
@Petercordes: Le changement de paligne finit en fait d'être une légère pessimisation, je ne sais pas exactement pourquoi. goodbolt.org/z/y3-dbh
Probablement un débit de shuffle de 1 sur horloge si vous êtes sur HASWELL / SKYLAKE. Les charges non alignées devraient être mieux là. Je n'ai pas regardé trop attentivement à la façon dont GCC la compilait et / ou des mélanges dans votre original, mais peut-être que c'était mieux que 3 Uops.
@Petercordes: Oui, je teste sur SKL. Il utilise trois ops pour le et / / ou la programmation du premier et très tôt (avant le shuffle précédent), et de manière intéressante de la réorganisation des deux derniers coups et écrit à la matrice de sortie ( goodbolt.org/z/lyxvsg ).
@Petercordes: il ressemble à la version pand / pand / por code> la version fonctionne légèrement mieux que le code> Palignr code> car ce dernier est en concurrence avec le
pshufb code> pour l'utilisation de Port d'exécution 5, tandis que le premier peut distribuer à travers les ports 0, 1 et 5.
Oui, Haswell et plus tard, une seule unité de shuffle, sur le port 5. Il est surprenant parce que vous n'aviez pas eu de goulot d'étranglement P5, alors je m'attendais à 3 Uops pouvant choisir l'un des P015 pour être aussi mauvais que 1 UOP pour P5 , de toute façon coûtant essentiellement un cycle supplémentaire de débit. Mais apparemment ce n'est pas la façon dont il fonctionne, au moins la façon dont GCC le compile. Mais de toute façon, c'est exactement pourquoi j'ai dit que Haswell et plus tard ferait mieux avec 4 charges non alignées (potentiellement) au lieu de Palignr code>.
Vous pouvez enregistrer sur des masques en utilisant et code> /
andn code> avec le même masque, au lieu de
et code> avec un masque inverse. Ou il y a
MOVQ XMM, XMM code>, mais qui ne peut qu'à zéro prolonger les byges basses 8, pas pour extraire le haut 8. (il y a un intrinsèque pour cela, cependant.). Fusionner le 8 vecteur High 8 avec le bas 8 d'un autre prend une instruction
MOVSD XMM, XMM CODE>, mais c'est un shuffle. AVX2
VPBLENDD CODE> est très efficace, de même que SSE4.1
blends / pd code> (1 uop pour n'importe quel port). Mais
PBLENDW code> ne fonctionne que sur le port 5, vous êtes donc bloqué avec des retards de dérivation ou le port de shuffle pour des mélanges entiers efficaces jusqu'à AVX2.
J'ai personnellement constaté que la mise en œuvre de ce qui suit m'a donné le meilleur résultat pour la conversion de BGR-24 en ArgB-32.
Ce code est exécuté vers une image d'une image alors que le code de vecteur de 128 bits présenté ci-dessus est arrivé à 14.5ms par image. P> précédemment, j'avais utilisé cette routine (environ 13,2 ms par image). Ici, Buff est un caractère non signé *. P> exécutant une macmini de 2,6 ghz / i7 2012. p> p> p>
De plus, on peut souhaiter examiner l'API récent de conversion de Vimage d'Apple ..., en particulier des routines telles que "Vimageconvert_RgB888ToArgB8888" pour la conversion de RVB 24 bits à 32 bits ArgB (ou BGRA). développeur.apple.com/library/mac/documentation/performance/...
FWIW Je ne peux pas reproduire ce résultat - essais sur I5-6200U (SkyLake) avec GCC 6.3.0 Utilisation -MSSSE3 -O3 CODE> I Get 1.57MS par (1920x1080) Image pour
Pixelfix Code> et 1.07MS par image pour
rgb_to_bgrx_sse code>.
Ummm ... à l'aide de Vimageconvert_RGB888ToArgB8888 est très très rapide (15x SpeedUp). P>
Au-dessus du code Pixelfix (≈6ms par image, maintenant sur le matériel récent) P>
Utilisation de Vimageconvert_RGB888ToArgB888, fileté (sur le matériel récent) P>
besoin de dire plus? p>
Un suivi ... à l'aide du code vectoriel 128 bits unique "RGB_TO_BGRX_SSE" ci-dessus a donné des résultats dans la plage 11 ms pour les mêmes tampons d'E / S de la taille. Vimage est le gagnant clair ici.
Utilisez-vous l'indicateur d'optimisation pour votre compilateur (lequel?)? Le compilateur fera souvent un meilleur travail d'optimisation du code, sans i> introduire une incorrecture. Quelles données de référence avez-vous collectées?
Pas une réponse ESS, mais avez-vous essayé de déranger votre boucle 4 fois de sorte que l'entrée commence toujours sur une adresse alignée? Ensuite, vous pouvez lire l'entrée d'un mot de machine à la fois plutôt que par ByTewise, avec un changement de vitesse spécialisé pour chaque position relative du pixel source. Comme Dana mentionne, il convient de voir comment bien le compilateur fonctionne sur des niveaux d'optimisation élevés (inspecter le code d'assembleur généré, en plus de l'analyse comparative), mais je doute qu'il soit suffisamment agressif pour dérouler la boucle et i> Divisez le point d'entrée en fonction de l'alignement de
dans code> tout seul.
De grandes questions. C'est simplement "O2" (pas O3) avec GCC4.6. Mon étui de référence est une itération de 10 000 gérées avec 512 en tant que "largeur". Merci pour les grandes réponses!