6
votes

Conversion vectorisée rapide de RVB en BGRA

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 RVB à BGRA forte> avec SSE forte> . Supposons une machine de 32 bits et souhaitez utiliser intrinsique forte>. J'ai de la difficulté à aligner les tampons de source et de destination à travailler avec des registres de 128 bits et de rechercher d'autres solutions de vectorisation avance.

La routine à être vectorisée est la suivante ... P>

gcc -O2 main.c


3 commentaires

Utilisez-vous l'indicateur d'optimisation pour votre compilateur (lequel?)? Le compilateur fera souvent un meilleur travail d'optimisation du code, sans 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 Divisez le point d'entrée en fonction de l'alignement de dans 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!


4 Réponses :


2
votes

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


4 commentaires

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.



10
votes

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


10 commentaires

Même GCC8.2 -O3 n'oblise pas la version de l'OP dans une charge de 4 octets. ICC et CLANG -O3 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 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 / _mm_alignr_epi8 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 ou punpcklqdq 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 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 palignr 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 Pour obtenir MOV + BSWAP + ou + MOV . (Mais c'est toujours 4 Uops Total ne comptant pas de surcharge de boucle pour Pointants + = 3 * déroulement et + = 4 * dérouler , nous ne pouvons donc approcher que 1 DWord Store par horloge Avec un énorme déroulement) ou sur Atom / Silvermont (mais pas haswell), movbe 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 la version fonctionne légèrement mieux que le Palignr car ce dernier est en concurrence avec le pshufb 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 .


Vous pouvez enregistrer sur des masques en utilisant et / andn avec le même masque, au lieu de et avec un masque inverse. Ou il y a MOVQ XMM, XMM , 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 , mais c'est un shuffle. AVX2 VPBLENDD est très efficace, de même que SSE4.1 blends / pd (1 uop pour n'importe quel port). Mais PBLENDW 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.



3
votes

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. xxx

précédemment, j'avais utilisé cette routine (environ 13,2 ms par image). Ici, Buff est un caractère non signé *. xxx

exécutant une macmini de 2,6 ghz / i7 2012.


2 commentaires

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 I Get 1.57MS par (1920x1080) Image pour Pixelfix et 1.07MS par image pour rgb_to_bgrx_sse .



3
votes

Ummm ... à l'aide de Vimageconvert_RGB888ToArgB8888 est très très rapide (15x SpeedUp).

Au-dessus du code Pixelfix (≈6ms par image, maintenant sur le matériel récent)


  1. 6.373520 MS
  2. 6.383363 MS
  3. 6.413560 MS
  4. 6.278606 MS
  5. 6.293607 MS
  6. 6.368118 MS
  7. 6.338904 MS
  8. 6.389385 MS
  9. 6.365495 MS

    Utilisation de Vimageconvert_RGB888ToArgB888, fileté (sur le matériel récent)


    1. 0.563649 MS
    2. 0.400387 MS
    3. 0.375198 MS
    4. 0.360898 MS
    5. 0.391278 MS
    6. 0.396797 MS
    7. 0.405534 MS
    8. 0.386495 MS
    9. 0.367621 MS

      besoin de dire plus?


1 commentaires

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.