Frage

Ich bin sehr neu für SIMD / SSE und ich versuche, einige einfache Bildfilterung (Unschärfe) zu tun. Der folgende Code Filter jedes Pixel eines 8-Bit-Grau Bitmap mit einem einfachen [1 2 1] in horizontaler Richtung gewichtet. Ich Summen von 16 Pixeln zu einem Zeitpunkt erzeugt wird.

Was ist mit diesem Code sehr schlecht scheint, zumindest für mich ist, dass es eine Menge Einsatz ist / Extrakt drin, die nicht sehr elegant ist und verlangsamt wahrscheinlich alles nach unten als auch. Gibt es einen besseren Weg, um Daten von einem reg in eine andere zu wickeln, wenn Verschiebung?

buf ist die Bilddaten, 16-Byte-ausgerichtet sind. w / h sind Breite und Höhe ein Vielfaches von 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);
}
War es hilfreich?

Lösung

Ich schlage vor, hält die benachbarten Pixel auf dem SSE-Register. Das heißt, halten das Ergebnis des _mm_slli_si128 / _mm_srli_si128 in einem SSE-Variable und alle des Einsatzes und Extrakts eliminiert. Meine Argumentation ist, dass bei älteren CPUs, die Einsatz / extract Anweisungen erfordern die Kommunikation zwischen den SSE-Einheiten und den Mehrzweckeinheiten, die langsamer ist viel als die Berechnung innerhalb SSE zu halten, auch wenn es auf den L1-Cache schwappt über.

Wenn das geschehen ist, sollte es nur vier 16-Bit-Verschiebungen (_mm_slli_si128, _mm_srli_si128, nicht mitgerechnet die Divison Verschiebung ) sein. Mein Vorschlag ist, eine Benchmark mit Ihrem Code zu tun, denn zu diesem Zeitpunkt kann Ihr Code bereits die Speicherbandbreite Grenze erreicht haben .. was bedeutet, Sie können nicht optimize mehr.

Wenn das Bild groß ist (größer als L2-Größe) und der Ausgang wird nicht bald wieder gelesen wird, versuchen Verwendung MOVNTDQ (_mm_stream_si128) zum Schreiben zurück. Nach mehreren Websites ist es in SSE2, obwohl Sie nochmals zu überprüfen wollen könnten.

SIMD-Tutorial:

Einige SIMD-Guru Websites:

Andere Tipps

Diese Art von Nachbarschaftsoperation war immer ein Schmerz mit SSE, bis SSE3.5 (aka SSSE3) kam, und PALIGNR (_mm_alignr_epi8) eingeführt wurde.

Wenn Sie nach hinten müssen die Kompatibilität mit SSE2 / SSE3 aber können Sie eine äquivalente Makro oder Inline-Funktion schreiben, die emuliert für SSE2 / SSE3 _mm_alignr_epi8 und die Tropfen bis hin zu _mm_alignr_epi8 wenn Targeting SSE3.5 / SSE4.

Ein weiterer Ansatz ist falsch ausgerichtet Lasten zu verwenden, um die verschobenen Daten zu bekommen - das ist relativ teuer bei älteren CPUs (etwa die doppelte Latenz und die Hälfte des Durchsatzes ausgerichtet Lasten), aber dies akzeptabel sein kann, abhängig von viel viel Rechen Sie tun pro Ladung. Es hat auch den Vorteil, dass auf aktuelles Intel-CPUs (Core i7) versetzt Lasten haben keine Strafe im Vergleich zu ausgerichteten Lasten, so dass Ihr Code recht effizient sein wird, auf Core i7 et al .

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top