Question

Je suis très nouveau pour SIMD / SSE et je suis en train de faire un peu de filtrage simple image (flou). Le code ci-dessous les filtres de chaque pixel d'un bitmap gris à 8 bits avec un simple [1 2 1] pondération dans le sens horizontal. Je crée des sommes de 16 pixels à la fois.

Ce qui semble très mal à ce code, au moins pour moi, est qu'il ya beaucoup d'insert / extrait dans ce qui est pas très élégant et ralentit probablement tout bas aussi bien. Y at-il une meilleure façon d'envelopper les données d'un reg dans un autre lors du changement?

buf est la donnée d'image, alignés sur 16 octets. w / h sont la largeur et la hauteur, un multiple de 16.

__m128i *p = (__m128i *) buf;
__m128i cur1, cur2, sum1, sum2, zeros, tmp1, tmp2, saved;
zeros = _mm_setzero_si128();
short shifted, last = 0, next;

// preload first row
cur1 = _mm_load_si128(p);
for (x = 1; x < (w * h) / 16; x++) {
    // unpack
    sum1 = sum2 = saved = cur1;
    sum1 = _mm_unpacklo_epi8(sum1, zeros);
    sum2 = _mm_unpackhi_epi8(sum2, zeros);
    cur1 = tmp1 = sum1;
    cur2 = tmp2 = sum2;
    // "middle" pixel
    sum1 = _mm_add_epi16(sum1, sum1);
    sum2 = _mm_add_epi16(sum2, sum2);
    // left pixel
    cur2 = _mm_slli_si128(cur2, 2);
    shifted = _mm_extract_epi16(cur1, 7);
    cur2 = _mm_insert_epi16(cur2, shifted, 0);
    cur1 = _mm_slli_si128(cur1, 2);
    cur1 = _mm_insert_epi16(cur1, last, 0);
    sum1 = _mm_add_epi16(sum1, cur1);
    sum2 = _mm_add_epi16(sum2, cur2);
    // right pixel
    tmp1 = _mm_srli_si128(tmp1, 2);
    shifted = _mm_extract_epi16(tmp2, 0);
    tmp1 = _mm_insert_epi16(tmp1, shifted, 7);
    tmp2 = _mm_srli_si128(tmp2, 2);
    // preload next row
    cur1 = _mm_load_si128(p + x);
    // we need the first pixel of the next row for the "right" pixel
    next = _mm_extract_epi16(cur1, 0) & 0xff;
    tmp2 = _mm_insert_epi16(tmp2, next, 7);
    // and the last pixel of last row for the next "left" pixel
    last = ((uint16_t) _mm_extract_epi16(saved, 7)) >> 8;
    sum1 = _mm_add_epi16(sum1, tmp1);
    sum2 = _mm_add_epi16(sum2, tmp2);
    // divide
    sum1 = _mm_srli_epi16(sum1, 2);
    sum2 = _mm_srli_epi16(sum2, 2);
    sum1 = _mm_packus_epi16(sum1, sum2);
    mm_store_si128(p + x - 1, sum1);
}
Était-ce utile?

La solution

je suggère garder les pixels voisins sur le registre SSE. C'est, garder le résultat de la SSE dans une variable _mm_slli_si128 / _mm_srli_si128 et éliminer tous l'insert et l'extrait. Mon raisonnement est que les processeurs plus anciens, les instructions d'insertion / extraction nécessitent une communication entre les unités SSE et les unités à usage général, ce qui est beaucoup plus lent que de garder le calcul au sein de l'ESS, même si elle se répand dans le cache L1.

Quand cela est fait, il devrait y avoir seulement quatre quarts de 16 bits (_mm_slli_si128, _mm_srli_si128, sans compter le décalage de divison ). Ma suggestion est de faire une référence avec votre code, car à ce moment votre code peut avoir déjà atteint la limite de bande passante mémoire .. ce qui signifie que vous ne pouvez pas optimize plus.

Si l'image est grande (plus grande que la taille L2) et la sortie ne sera pas lu bientôt de retour, essayez d'utiliser MOVNTDQ (_mm_stream_si128) pour écrire de nouveau. Selon plusieurs sites Web, il est en SSE2, bien que vous pouvez vérifier.

SIMD tutoriel:

Certains sites gourou SIMD:

Autres conseils

Ce type d'opération de quartier a toujours été une douleur avec SSE, jusqu'à ce que SSE3.5 (aka SSSE3) est venu, et PALIGNR (_mm_alignr_epi8) a été introduit.

Si vous avez besoin d'une compatibilité descendante avec SSE2 / SSE3 cependant, vous pouvez écrire une macro ou une fonction équivalente en ligne qui émule _mm_alignr_epi8 SSE2 / SSE3 et qui tombe à travers de _mm_alignr_epi8 quand SSE3.5 ciblage / SSE4.

Une autre approche consiste à utiliser des charges mal alignées pour obtenir les données décalées - ce qui est relativement cher sur les processeurs plus âgés (environ deux fois plus de temps d'attente et la moitié du débit des charges alignées), mais cela peut être acceptable en fonction de calcul beaucoup plus que vous faites par charge. Il a également l'avantage que sur les charges processeurs Intel (Core i7) courant désalignés ont pas de pénalité par rapport aux charges alignées, de sorte que votre code sera très efficace sur Core i7 et al .

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