Pregunta

estoy realizando una lectura dispersa de datos de 8 bits desde un archivo (De-entrelazar un archivo de onda 64 canales). entonces estoy combinándolos para ser un único flujo de bytes. El problema que estoy teniendo es con mi reconstrucción de los datos para escribir.

Básicamente estoy leyendo en 16 bytes y luego la construcción de ellos en una sola variable __m128i y luego usando _mm_stream_ps para escribir el valor de vuelta a la memoria. Sin embargo tengo algunos resultados de rendimiento impares.

En mi primer esquema utilizo el _mm_set_epi8 intrínseca para establecer mi __m128i de la siguiente manera:

    const __m128i packedSamples = _mm_set_epi8( sample15,   sample14,   sample13,   sample12,   sample11,   sample10,   sample9,    sample8,
                                                sample7,    sample6,    sample5,    sample4,    sample3,    sample2,    sample1,    sample0 );

Básicamente lo dejo todo para el compilador de decidir cómo optimizarlo para dar el mejor rendimiento. Esto proporciona un rendimiento peor. MI prueba se ejecuta en ~ 0.195 segundos.

En segundo lugar me trataron de fusionar hacia abajo mediante el uso de 4 _mm_set_epi32 instrucciones de embalaje y luego hacia abajo:

    const __m128i samples0      = _mm_set_epi32( sample3, sample2, sample1, sample0 );
    const __m128i samples1      = _mm_set_epi32( sample7, sample6, sample5, sample4 );
    const __m128i samples2      = _mm_set_epi32( sample11, sample10, sample9, sample8 );
    const __m128i samples3      = _mm_set_epi32( sample15, sample14, sample13, sample12 );

    const __m128i packedSamples0    = _mm_packs_epi32( samples0, samples1 );
    const __m128i packedSamples1    = _mm_packs_epi32( samples2, samples3 );
    const __m128i packedSamples     = _mm_packus_epi16( packedSamples0, packedSamples1 );

Esto mejora el rendimiento un poco. Mi prueba ahora se ejecuta en ~ 0,15 segundos. Parece contrario a la intuición de que el rendimiento mejoraría al hacer esto como supongo que esto es exactamente lo que está haciendo _mm_set_epi8 de todos modos ...

Mi último intento fue utilizar un poco de código que tengo de hacer cuatro CC de la manera antigua (con los cambios y ORS) y luego ponerlos en un __m128i utilizando un único _mm_set_epi32.

    const GCui32 samples0       = MakeFourCC( sample0, sample1, sample2, sample3 );
    const GCui32 samples1       = MakeFourCC( sample4, sample5, sample6, sample7 );
    const GCui32 samples2       = MakeFourCC( sample8, sample9, sample10, sample11 );
    const GCui32 samples3       = MakeFourCC( sample12, sample13, sample14, sample15 );
    const __m128i packedSamples = _mm_set_epi32( samples3, samples2, samples1, samples0 );

Esto proporciona un rendimiento aún mejor. Tomando ~ 0,135 segundos para ejecutar mi prueba. Realmente estoy empezando a confundirse.

Así que he intentado un simple sistema de escritura de bytes de bytes de lectura y que es siempre tan ligeramente más rápido que incluso el último método.

Entonces, ¿qué está pasando? Todo esto parece contrario a la intuición para mí.

He considerado la idea de que los retrasos se están produciendo en los _mm_stream_ps porque estoy suministro de datos demasiado rápido, pero luego lo haría para obtener exactamente los mismos resultados a cabo todo lo que hago. ¿Es posible que los 2 primeros métodos significan que las cargas no puedan 16 se distribuyen a través del lazo para ocultar la latencia? Si es así ¿por qué es esto? Sin duda, una intrínseca permite al compilador para hacer optimizaciones como y donde le plazca .. pensé que era el punto ... También seguramente realizando 16 operaciones de lectura y escritura 16 será mucho más lento que 16 operaciones de lectura y escritura 1 con un grupo de malabares SSE instrucciones ... Después de todo su la lecturas y escrituras que son el poco lento!

Cualquier persona con cualquier idea que hay de nuevo será muy apreciada! : D

Editar: En relación con el comentario a continuación dejé de pre-carga de los bytes como constantes y changedit a esto:

    const __m128i samples0      = _mm_set_epi32( *(pSamples + channelStep3), *(pSamples + channelStep2), *(pSamples + channelStep1), *(pSamples + channelStep0) );
    pSamples    += channelStep4;
    const __m128i samples1      = _mm_set_epi32( *(pSamples + channelStep3), *(pSamples + channelStep2), *(pSamples + channelStep1), *(pSamples + channelStep0) );
    pSamples    += channelStep4;
    const __m128i samples2      = _mm_set_epi32( *(pSamples + channelStep3), *(pSamples + channelStep2), *(pSamples + channelStep1), *(pSamples + channelStep0) );
    pSamples    += channelStep4;
    const __m128i samples3      = _mm_set_epi32( *(pSamples + channelStep3), *(pSamples + channelStep2), *(pSamples + channelStep1), *(pSamples + channelStep0) );
    pSamples    += channelStep4;

    const __m128i packedSamples0    = _mm_packs_epi32( samples0, samples1 );
    const __m128i packedSamples1    = _mm_packs_epi32( samples2, samples3 );
    const __m128i packedSamples     = _mm_packus_epi16( packedSamples0, packedSamples1 );

y esto mejora el rendimiento de ~ 0,143 segundos. Sitll no tan buena como la implementación en C recta ...

Editar Una vez más: La mejor actuación que estoy haciendo hasta ahora es

    // Load the samples.
    const GCui8 sample0     = *(pSamples + channelStep0);
    const GCui8 sample1     = *(pSamples + channelStep1);
    const GCui8 sample2     = *(pSamples + channelStep2);
    const GCui8 sample3     = *(pSamples + channelStep3);

    const GCui32 samples0   = Build32( sample0, sample1, sample2, sample3 );
    pSamples += channelStep4;

    const GCui8 sample4     = *(pSamples + channelStep0);
    const GCui8 sample5     = *(pSamples + channelStep1);
    const GCui8 sample6     = *(pSamples + channelStep2);
    const GCui8 sample7     = *(pSamples + channelStep3);

    const GCui32 samples1   = Build32( sample4, sample5, sample6, sample7 );
    pSamples += channelStep4;

    // Load the samples.
    const GCui8 sample8     = *(pSamples + channelStep0);
    const GCui8 sample9     = *(pSamples + channelStep1);
    const GCui8 sample10    = *(pSamples + channelStep2);
    const GCui8 sample11    = *(pSamples + channelStep3);

    const GCui32 samples2       = Build32( sample8, sample9, sample10, sample11 );
    pSamples += channelStep4;

    const GCui8 sample12    = *(pSamples + channelStep0);
    const GCui8 sample13    = *(pSamples + channelStep1);
    const GCui8 sample14    = *(pSamples + channelStep2);
    const GCui8 sample15    = *(pSamples + channelStep3);

    const GCui32 samples3   = Build32( sample12, sample13, sample14, sample15 );
    pSamples += channelStep4;

    const __m128i packedSamples = _mm_set_epi32( samples3, samples2, samples1, samples0 );

    _mm_stream_ps( pWrite + 0,  *(__m128*)&packedSamples ); 

Esto me da el procesamiento de ~ 0.095 segundos, que es considerablemente mejor. Yo no parece ser capaz de acercarse con SSE aunque ... Todavía estoy confundido por eso, pero .. ho hum.

¿Fue útil?

Solución

Tal vez el compilador está tratando de poner todos los argumentos de la intrínseca en los registros a la vez. Usted no quiere acceder a que muchas variables a la vez sin la organización de ellos.

En lugar de declarar un identificador separado para cada muestra, tratar de ponerlos en un char[16]. El compilador promoverá los 16 valores a los registros que estime conveniente, siempre y cuando usted no toma la dirección de cualquier cosa dentro de la matriz. Se puede añadir una etiqueta __aligned__ (o lo que sea usos VC ++) y tal vez evitar la intrínseca por completo. De lo contrario, llamando a la intrínseca con ( sample[15], sample[14], sample[13] … sample[0] ) debe hacer el trabajo del compilador más fácil o al menos no hacer daño.


Editar Estoy bastante seguro de que está luchando contra un derrame de registro, pero esa sugerencia probablemente apenas almacenar los bytes de forma individual, que no es lo que desea. Creo que mi consejo es que intercalar de su último intento (usando MakeFourCC) con las operaciones de lectura, para asegurarse de que está programado correctamente y sin viajes de ida y vuelta a la pila. Por supuesto, la inspección de código objeto es la mejor manera de asegurar que.

En esencia, se está transmitiendo datos en el archivo de registro y luego streaming de vuelta. Usted no quiere sobrecargar antes de que sea tiempo para vaciar los datos.

Otros consejos

VS es notablemente malos para la optimización de los intrínsecos. Especialmente mover datos desde y hacia los registros de la ESS. Las características intrínsecas sí se utilizan bastante bien sin embargo ....

Lo que se ve es que se trata de llenar la ESS se registra en este monstruo:

00AA100C  movzx       ecx,byte ptr [esp+0Fh]  
00AA1011  movzx       edx,byte ptr [esp+0Fh]  
00AA1016  movzx       eax,byte ptr [esp+0Fh]  
00AA101B  movd        xmm0,eax  
00AA101F  movzx       eax,byte ptr [esp+0Fh]  
00AA1024  movd        xmm2,edx  
00AA1028  movzx       edx,byte ptr [esp+0Fh]  
00AA102D  movd        xmm1,ecx  
00AA1031  movzx       ecx,byte ptr [esp+0Fh]  
00AA1036  movd        xmm4,ecx  
00AA103A  movzx       ecx,byte ptr [esp+0Fh]  
00AA103F  movd        xmm5,edx  
00AA1043  movzx       edx,byte ptr [esp+0Fh]  
00AA1048  movd        xmm3,eax  
00AA104C  movzx       eax,byte ptr [esp+0Fh]  
00AA1051  movdqa      xmmword ptr [esp+60h],xmm0  
00AA1057  movd        xmm0,edx  
00AA105B  movzx       edx,byte ptr [esp+0Fh]  
00AA1060  movd        xmm6,eax  
00AA1064  movzx       eax,byte ptr [esp+0Fh]  
00AA1069  movd        xmm7,ecx  
00AA106D  movzx       ecx,byte ptr [esp+0Fh]  
00AA1072  movdqa      xmmword ptr [esp+20h],xmm4  
00AA1078  movdqa      xmmword ptr [esp+80h],xmm0  
00AA1081  movd        xmm4,ecx  
00AA1085  movzx       ecx,byte ptr [esp+0Fh]  
00AA108A  movdqa      xmmword ptr [esp+70h],xmm2  
00AA1090  movd        xmm0,eax  
00AA1094  movzx       eax,byte ptr [esp+0Fh]  
00AA1099  movdqa      xmmword ptr [esp+10h],xmm4  
00AA109F  movdqa      xmmword ptr [esp+50h],xmm6  
00AA10A5  movd        xmm2,edx  
00AA10A9  movzx       edx,byte ptr [esp+0Fh]  
00AA10AE  movd        xmm4,eax  
00AA10B2  movzx       eax,byte ptr [esp+0Fh]  
00AA10B7  movd        xmm6,edx  
00AA10BB  punpcklbw   xmm0,xmm1  
00AA10BF  punpcklbw   xmm2,xmm3  
00AA10C3  movdqa      xmm3,xmmword ptr [esp+80h]  
00AA10CC  movdqa      xmmword ptr [esp+40h],xmm4  
00AA10D2  movd        xmm4,ecx  
00AA10D6  movdqa      xmmword ptr [esp+30h],xmm6  
00AA10DC  movdqa      xmm1,xmmword ptr [esp+30h]  
00AA10E2  movd        xmm6,eax  
00AA10E6  punpcklbw   xmm4,xmm5  
00AA10EA  punpcklbw   xmm4,xmm0  
00AA10EE  movdqa      xmm0,xmmword ptr [esp+50h]  
00AA10F4  punpcklbw   xmm1,xmm0  
00AA10F8  movdqa      xmm0,xmmword ptr [esp+70h]  
00AA10FE  punpcklbw   xmm6,xmm7  
00AA1102  punpcklbw   xmm6,xmm2  
00AA1106  movdqa      xmm2,xmmword ptr [esp+10h]  
00AA110C  punpcklbw   xmm2,xmm0  
00AA1110  movdqa      xmm0,xmmword ptr [esp+20h]  
00AA1116  punpcklbw   xmm1,xmm2  
00AA111A  movdqa      xmm2,xmmword ptr [esp+40h]  
00AA1120  punpcklbw   xmm2,xmm0  
00AA1124  movdqa      xmm0,xmmword ptr [esp+60h]  
00AA112A  punpcklbw   xmm3,xmm0  
00AA112E  punpcklbw   xmm2,xmm3  
00AA1132  punpcklbw   xmm6,xmm4  
00AA1136  punpcklbw   xmm1,xmm2  
00AA113A  punpcklbw   xmm6,xmm1  

Esto funciona mucho mejor y (debería) ser fácilmente más rápido:

__declspec(align(16)) BYTE arr[16] = { sample15, sample14, sample13, sample12, sample11, sample10, sample9, sample8, sample7, sample6, sample5, sample4, sample3, sample2, sample1, sample0 };

__m128i packedSamples = _mm_load_si128( (__m128i*)arr );

Construir mi propio banco de pruebas:

void    f()
{
    const int steps = 1000000;
    BYTE* pDest = new BYTE[steps*16+16];
    pDest += 16 - ((ULONG_PTR)pDest % 16);
    BYTE* pSrc = new BYTE[steps*16*16];

    const int channelStep0 = 0;
    const int channelStep1 = 1;
    const int channelStep2 = 2;
    const int channelStep3 = 3;
    const int channelStep4 = 16;

    __int64 freq;
    QueryPerformanceFrequency( (LARGE_INTEGER*)&freq );
    __int64 start = 0, end;
    QueryPerformanceCounter( (LARGE_INTEGER*)&start );

    for( int step = 0; step < steps; ++step )
    {
        __declspec(align(16)) BYTE arr[16];
        for( int j = 0; j < 4; ++j )
        {
            //for( int i = 0; i < 4; ++i )
            {
                arr[0+j*4] = *(pSrc + channelStep0);
                arr[1+j*4] = *(pSrc + channelStep1);
                arr[2+j*4] = *(pSrc + channelStep2);
                arr[3+j*4] = *(pSrc + channelStep3);
            }
            pSrc += channelStep4;
        }

#if test1
// test 1 with C
        for( int i = 0; i < 16; ++i )
        {
            *(pDest + step * 16 + i) = arr[i];
        }
#else
// test 2 with SSE load/store    
        __m128i packedSamples = _mm_load_si128( (__m128i*)arr );
        _mm_stream_si128( ((__m128i*)pDest) + step, packedSamples );
#endif
    }

    QueryPerformanceCounter( (LARGE_INTEGER*)&end );

    printf( "%I64d", (end - start) * 1000 / freq );

}

Para mí la prueba 2 es más rápido que la prueba 1.

Qué hago algo mal? No es este el código que está utilizando? ¿Qué me he perdido? ¿Es sólo para mí?

El uso de optimizaciones del compilador intrínsecos se rompe!

El punto de las funciones intrínsecas es insertar códigos de operación que el compilador no conoce en la corriente de códigos de operación el compilador sabe acerca y ha generado. A menos que el compilador se da algunos metadatos sobre el código de operación y cómo afecta a los registros y la memoria, el compilador no puede asumir que los datos se conserva después de ejecutar la intrínseca. Esto realmente duele la parte optimización del compilador -. No puede cambiar el orden de las instrucciones alrededor del intrínseca, no puede asumir registros no se ven afectadas y así sucesivamente

Creo que la mejor manera de optimizar esto es mirar el cuadro más grande - debe tener en cuenta todo el proceso de la lectura de los datos de origen a la escritura de la salida final. optimizaciones micro rara vez se dan grandes resultados, a menos que usted está haciendo algo muy mal para empezar.

Tal vez, si el detalle requerido alguien entrada y salida de aquí podría sugerir un método óptimo para manejarlo.

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