Question

J'ai essayé d'optimiser un code qui gère les données de pixels bruts. Actuellement, l'implémentation C ++ du code est trop lente, donc j'ai essayé de faire des terrains en utilisant SSE Intrinsics (SSE / 2/3 n'utilisant pas 4) avec MSVC 2008. Considérant que c'est la première fois que je creuse dans ce bas, i ' ve a fait de bons progrès.

Malheureusement, je suis arrivé à un morceau de code particulier qui m'a coincé:

//Begin bad/suboptimal SSE code
__m128i vnMask  = _mm_set1_epi16(0x0001);
__m128i vn1     = _mm_and_si128(vnFloors, vnMask);

for(int m=0; m < PBS_SSE_PIXELS_PROCESS_AT_ONCE; m++)
{
    bool bIsEvenFloor   = vn1.m128i_u16[m]==0;

    vnPxChroma.m128i_u16[m] = 
        m%2==0 
            ?
        (bIsEvenFloor ? vnPxCeilChroma.m128i_u16[m] : vnPxFloorChroma.m128i_u16[m])
            :
        (bIsEvenFloor ? vnPxFloorChroma.m128i_u16[m] : vnPxCeilChroma.m128i_u16[m]);
}

Actuellement, je suis défautant à l'utilisation d'une implémentation C ++ Pour cette section parce que je ne peux pas tout à fait comprendre comment cela peut être optimisé en utilisant SSE - je trouve que l'intrinssique SSE pour les comparaisons est un peu délicat.

Toutes les suggestions / conseils seraient très appréciées.

ÉDITER:Le code C ++ équivalent qui gère un seul pixel à la fois serait:

short pxCl=0, pxFl=0;
short uv=0; // chroma component of pixel
short y=0;  // luma component of pixel

for(int i = 0; i < end-of-line, ++i)
{
    //Initialize pxCl, and pxFL
    //...

    bool bIsEvenI       = (i%2)==0;
    bool bIsEvenFloor   = (m_pnDistancesFloor[i] % 2)==0;

    uv = bIsEvenI ==0 
        ?
    (bIsEvenFloor ? pxCl : pxFl)
        :
    (bIsEvenFloor ? pxFl : pxCl);

    //Merge the Y/UV of the pixel;
    //...
}

Fondamentalement, je fais un étirement de bord non linéaire de 4: 3 à 16: 9.

Était-ce utile?

La solution

OK, donc je ne sais pas ce que fait ce code, mais je sais que vous demandez comment optimiser les opérateurs de ternery et obtenir cette partie du code fonctionnant uniquement dans SSE. Dans une première étape, je recommanderais d'essayer une approche utilisant des drapeaux entiers et une multiplication pour éviter un opérateur conditionnel. Par exemple:

Cette section

for(int m=0; m < PBS_SSE_PIXELS_PROCESS_AT_ONCE; m++)
{
    bool bIsEvenFloor   = vn1.m128i_u16[m]==0;      

    vnPxChroma.m128i_u16[m] = m%2==0 ?  
      (bIsEvenFloor ? vnPxCeilChroma.m128i_u16[m] : vnPxFloorChroma.m128i_u16[m])  : 
      (bIsEvenFloor ? vnPxFloorChroma.m128i_u16[m] : vnPxCeilChroma.m128i_u16[m]); 
}

Est syntaxiquement équivalent à cela

// DISCLAIMER: Untested both in compilation and execution

// Process all m%2=0 in steps of 2
for(int m=0; m < PBS_SSE_PIXELS_PROCESS_AT_ONCE; m+=2)
{
    // This line could surely pack muliple u16s into one SSE2 register
    uint16 iIsOddFloor = vn1.m128i_u16[m] & 0x1 // If u16[m] == 0, result is 0
    uint16 iIsEvenFloor = iIsOddFloor ^ 0x1 // Flip 1 to 0, 0 to 1

    // This line could surely perform an SSE2 multiply across multiple registers
    vnPxChroma.m128i_u16[m] = iIsEvenFloor * vnPxCeilChroma.m128i_u16[m] + 
                              iIsOddFloor * vnPxFloorChroma.m128i_u16[m]
}

// Process all m%2!=0 in steps of 2
for(int m=1; m < PBS_SSE_PIXELS_PROCESS_AT_ONCE; m+=2)
{
    uint16 iIsOddFloor = vn1.m128i_u16[m] & 0x1 // If u16[m] == 0, result is 0
    uint16 iIsEvenFloor = iIsOddFloor ^ 0x1 // Flip 1 to 0, 0 to 1

    vnPxChroma.m128i_u16[m] = iIsEvenFloor * vnPxFloorChroma.m128i_u16[m] + 
                              iIsOddFloor * vnPxCeilChroma.m128i_u16[m]
}

Fondamentalement, en divisant en deux boucles, vous perdez l'amélioration des performances de l'accès à la mémoire série, mais supprimez une opération de modulo et deux opérateurs conditionnels.

Maintenant, vous dites, vous remarquez qu'il y a deux opérateurs booléens par boucle ainsi que les multiples que je pourrais ajouter ne sont pas des implémentations intrinsèques SSE. Qu'est-ce qui est stocké dans votre tableau VN1.M123I_U16 []? Est-ce que des zéros et des uns? Si c'est le cas, vous n'avez pas besoin de cette partie et pouvez le supprimer. Sinon, pouvez-vous normaliser vos données dans ce tableau pour n'en avoir que des zéros et des zéros? Si le tableau VN1.M123I_U16 n'en contient que ceux et les zéros, ce code devient

uint16 iIsOddFloor  = vn1.m128i_u16[m]
uint16 iIsEvenFloor = iIsOddFloor ^ 0x1 // Flip 1 to 0, 0 to 1

Vous remarquerez également que je n'utilise pas de multiples SSE pour effectuer le isEvenFloor * vnPx... part ni pour stocker le iIsEvenFloor et iIsOddFloor registres. Je suis désolé, je ne me souviens pas de l'intrinsèque SSE pour U16 Multiply / Enregistrez-vous du haut, mais j'espère néanmoins que cette approche sera utile. Certaines optimisations à laquelle vous devriez consulter:

// This line could surely pack muliple u16s into one SSE2 register
uint16 iIsOddFloor = vn1.m128i_u16[m] & 0x1 // If u16[m] == 0, result is 0
uint16 iIsEvenFloor = iIsOddFloor ^ 0x1 // Flip 1 to 0, 0 to 1

// This line could surely perform an SSE2 multiply across multiple registers
vnPxChroma.m128i_u16[m] = iIsEvenFloor * vnPxCeilChroma.m128i_u16[m] + 
                          iIsOddFloor * vnPxFloorChroma.m128i_u16[m] 

Dans cette section du code que vous avez publié et ma modification, nous n'utilisons toujours pas pleinement l'intrinssique SSE1 / 2/3, mais cela pourrait fournir des points sur la façon dont cela pourrait être fait (comment vectoriser le code).

Enfin, je dirais de tout tester. Exécutez le code ci-dessus inchangé et le profile avant d'apporter des modifications et le profilage à nouveau. Les numéros de performance réels peuvent vous surprendre!


Mise à jour 1:

J'ai traversé le Documentation Intel SIMD Intrinsics pour choisir des intrinsèques pertinents qui pourraient être utiles à cela. Jetez spécifiquement un œil à Bitwise XOR, et et Mult / Add

__M128 Types de données
Le type de données __M128I peut contenir seize 8 bits, huit 16 bits, quatre valeurs entières de 32 bits ou 64 bits.

__M128I _MM_ADD_EPI16 (__ M128I A, __M128I B)
Ajoutez les 8 entiers 16 bits signés ou non signés dans A à 8 entiers 16 bits signés ou non signés en B

__M128I _MM_MULHI_EPU16 (__ M128I A, __M128I B)
Multiplie les 8 entiers 16 bits non signés de A par les entiers 16 bits non signés de b. Emporte les 16 bits supérieurs des résultats 32 bits non signés

R0 = HiWord (A0 * B0)
R1 = HiWord (A1 * B1)
R2 = HiWord (A2 * B2)
R3 = HiWord (A3 * B3)
..
R7 = HiWord (A7 * B7)

__M128I _MM_MULLO_EPI16 (__ M128I A, __M128I B)
Multiplie les 8 entiers 16 bits signés ou non signés de A par les entiers 16 bits signés ou non signés de b. Emporte les 16 bits supérieurs des résultats 32 bits signés ou non signés ou non signés

R0 = loword (a0 * b0)
R1 = loword (a1 * b1)
R2 = loword (a2 * b2)
R3 = Loword (A3 * B3)
..
R7 = Loword (A7 * B7)

__M128I _MM_AND_SI128 (__ M128I A, __M128I B)
Effectuez un bit et de la valeur 128 bits dans M1 avec la valeur 128 bits en M2.

__M128I _MM_ANDNOT_SI128 (__ M128I A, __M128I B)
Calcule le bit et la valeur 128 bits en B et le bit de la valeur 128 bit en a.

__M128I _MM_XOR_SI128 (__ M128I A, __M128I B)
Effectuez un XOR bit de la valeur 128 bits dans M1 avec la valeur 128 bits en M2.

Également à partir de votre exemple de code pour référence

uint16 u1 = u2 = u3 ... = u15 = 0x1
__m128i vnmask = _mm_set1_epi16 (0x0001); // Définit les 8 valeurs entières 16 bits signées.

uint16 vn1 [i] = vnfloors [i] & 0x1
__m128i vn1 = _mm_and_si128 (vnfloors, vnmask); // calcule le bit et la valeur 128 bits dans A et la valeur 128 bits en b.

Autres conseils

Andrew Vos suggestions m'ont conduit sur le chemin d'une solution presque optimale.

En utilisant une combinaison d'une table de vérité et d'une carte Karnaugh, j'ai découvert que le code

 uv = bIsEvenI ==0 
    ?
(bIsEvenFloor ? pxCl : pxFl)
    :
(bIsEvenFloor ? pxFl : pxCl);

Bouillie en étant une fonction! xor (pas xor). Dès lors, j'ai pu utiliser la vectorisation SSE pour optimiser la solution:

//Use the mask with bit AND to check if even/odd
__m128i vnMask              = _mm_set1_epi16(0x0001);

//Set the bit to '1' if EVEN, else '0'
__m128i vnFloorsEven        = _mm_andnot_si128(vnFloors, vnMask);
__m128i vnMEven             = _mm_set_epi16
    (
        0,  //m==7
        1,
        0,
        1,
        0,
        1,
        0,  //m==1
        1   //m==0
    );


// Bit XOR the 'floor' values and 'm'
__m128i vnFloorsXorM        = _mm_xor_si128(vnFloorsEven, vnMEven);

// Now perform our bit NOT
__m128i vnNotFloorsXorM     = _mm_andnot_si128(vnFloorsXorM, vnMask);

// This is the C++ ternary replacement - using multipilaction
__m128i vnA                 = _mm_mullo_epi16(vnNotFloorsXorM, vnPxFloorChroma);
__m128i vnB                 = _mm_mullo_epi16(vnFloorsXorM, vnPxCeilChroma);

// Set our pixels - voila!
vnPxChroma                  = _mm_add_epi16(vnA, vnB);

Merci pour votre aide...

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