Pregunta

Estoy muy nuevo en SIMD / SSE y yo estoy tratando de hacer un poco de imagen simple filtración (visión borrosa). El código siguiente filtros cada píxel de un mapa de bits de grises de 8 bits con un simple [1 2 1] ponderación en dirección horizontal. Estoy creando sumas de 16 píxeles a la vez.

Lo que parece muy malo de este código, al menos para mí, es que hay una gran cantidad de inserción / extracto en ella, lo cual no es muy elegante y probablemente retrasa todo también. ¿Hay una mejor manera de envolver los datos de un registro a otro cuando se cambia?

buf es los datos de imagen, de 16 bytes alineados. w / h son anchura y altura, múltiplos 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);
}
¿Fue útil?

Solución

Sugiero mantener los píxeles vecinos en el registro SSE. Es decir, mantener el resultado de la _mm_slli_si128 / _mm_srli_si128 en una variable de SSE, y eliminar todas las de la inserción y extracción. Mi razonamiento es que en CPUs antiguas, las instrucciones de inserción / extracto requieren la comunicación entre las unidades de la ESS y las unidades de propósito general, que es mucho más lento que manteniendo el cálculo dentro de SSE, incluso si se derrama a la caché L1.

Cuando se hace eso, no debe haber sólo cuatro turnos de 16 bits (_mm_slli_si128, _mm_srli_si128, sin contar el cambio Divison ). Mi sugerencia es hacer un punto de referencia con su código, porque para entonces su código puede ya han alcanzado el límite de ancho de banda de memoria .. lo que significa que no se puede optimizar el nunca más.

Si la imagen es grande (más grande que el tamaño L2) y no se leerá la salida de vuelta pronto, trate uso MOVNTDQ (_mm_stream_si128) para escribir espalda. De acuerdo a varios sitios web, que está en SSE2, aunque es posible que desee una doble comprobación.

SIMD tutorial:

Algunos sitios web gurú SIMD:

Otros consejos

Este tipo de operación barrio siempre fue un dolor con SSE, hasta SSE3.5 (también conocido como SSSE3) llegó, y fue introducido PALIGNR (_mm_alignr_epi8).

Si necesita compatibilidad hacia atrás con SSE2 / SSE3 sin embargo, usted puede escribir una macro equivalente o función en línea que emula _mm_alignr_epi8 para SSE2 / SSE3 y que cae a través de _mm_alignr_epi8 cuando apuntan a SSE3.5 / SSE4.

Otro enfoque es utilizar cargas desalineados para obtener los datos desplazados - esto es relativamente caro en las CPU de mayor edad (más o menos el doble de la latencia y la mitad del rendimiento de cargas alineadas), pero esto puede ser aceptable dependiendo mucho, mucho cálculo que está haciendo por carga. También tiene la ventaja de que en la actual CPU de Intel (Core i7) cargas desalineadas tienen ninguna pena en comparación con cargas alineados, por lo que su código será bastante eficiente en Core i7 et al .

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top