2009-11-11 9 views
29

Estoy procesando imágenes en C que requieren copiar grandes cantidades de datos en la memoria: la fuente y el destino nunca se superponen.memcpy muy rápido para el procesamiento de imágenes?

¿Cuál es la manera más rápida absoluta de hacer esto en la plataforma x86 utilizando GCC (donde SSE, SSE2 pero NO SSE3 están disponibles)?

Espero que la solución sea en ensamblaje o utilizando intrínsecos GCC?

me encontré con el siguiente enlace, pero no tienen idea de si es la mejor manera de hacerlo (el autor también dice que tiene algunos errores): http://coding.derkeiler.com/Archive/Assembler/comp.lang.asm.x86/2006-02/msg00123.html

EDIT: tenga en cuenta que una copia es necesaria, no puedo tener que copiar los datos (podría explicar por qué pero le ahorraré la explicación :))

+0

¿Puedes escribir tu código para que la copia no sea necesaria en primer lugar? – Ron

+0

Ron, no, no puedo :( – horseyguy

+1

Si puede obtener el compilador de Intel, puede tener mejores posibilidades de que el optimizador se convierta en instrucciones de la CPU de vector –

Respuesta

38

Cortesía de William Chan y Google. 30-70% más rápido que el establecimiento de memoria en Microsoft Visual Studio 2005.

void X_aligned_memcpy_sse2(void* dest, const void* src, const unsigned long size) 
{ 

    __asm 
    { 
    mov esi, src; //src pointer 
    mov edi, dest; //dest pointer 

    mov ebx, size; //ebx is our counter 
    shr ebx, 7;  //divide by 128 (8 * 128bit registers) 


    loop_copy: 
     prefetchnta 128[ESI]; //SSE2 prefetch 
     prefetchnta 160[ESI]; 
     prefetchnta 192[ESI]; 
     prefetchnta 224[ESI]; 

     movdqa xmm0, 0[ESI]; //move data from src to registers 
     movdqa xmm1, 16[ESI]; 
     movdqa xmm2, 32[ESI]; 
     movdqa xmm3, 48[ESI]; 
     movdqa xmm4, 64[ESI]; 
     movdqa xmm5, 80[ESI]; 
     movdqa xmm6, 96[ESI]; 
     movdqa xmm7, 112[ESI]; 

     movntdq 0[EDI], xmm0; //move data from registers to dest 
     movntdq 16[EDI], xmm1; 
     movntdq 32[EDI], xmm2; 
     movntdq 48[EDI], xmm3; 
     movntdq 64[EDI], xmm4; 
     movntdq 80[EDI], xmm5; 
     movntdq 96[EDI], xmm6; 
     movntdq 112[EDI], xmm7; 

     add esi, 128; 
     add edi, 128; 
     dec ebx; 

     jnz loop_copy; //loop please 
    loop_copy_end: 
    } 
} 

Usted puede ser capaz de optimizarlo en función de su situación más exacta y las posibles hipótesis que son capaces de hacer.

Es posible que también desee verificar la fuente de memcpy (memcpy.asm) y quitar su manejo especial de casos. ¡Es posible optimizar aún más!

+6

Nota: el rendimiento de esta memcopy dependerá en gran medida de la cantidad de datos para copiar y el tamaño de la caché. Por ejemplo, las preejecuciones y los movimientos no temporales pueden empantanar el rendimiento para copias más pequeñas (ajustadas a L2) en comparación con las movdqa normales. –

+2

barandilla: no olvides enviarle por correo electrónico que usaste su código en tu proyecto;) [http://williamchan.ca/portfolio/assembly/ssememcpy/source/viewsource.php?id=readme.txt] – ardsrk

+3

Recuerdo leyendo este código en un manual AMD64 primero. Y el código no es óptimo en Intel, donde tiene problemas de alias del banco de caché. – hirschhornsalz

2

Si está en Windows, use las API DirectX, que tiene GPU -optimized rutinas específicas para el manejo de gráficos (¿Qué tan rápido podría ser? Su CPU no está cargada. Haga algo más mientras la GPU lo mastica).

Si quiere ser agnóstico del sistema operativo, intente OpenGL.

No juegue con el ensamblador, ya que es muy probable que fracase miserablemente para superar a los ingenieros de software con más de 10 años de experiencia en creación de bibliotecas.

+1

. Necesito que se realice en MEMORIA, es decir, no puede suceder en la GPU. :) Además, no pretendo superar las funciones de la biblioteca (por eso hago la pregunta aquí), pero estoy seguro de que hay alguien en stackoverflow que puede superar las libs :) Además, los escritores de librerías suelen estar restringidos por requisitos de portabilidad, como dije, solo me importa la plataforma x86, por lo que tal vez sea posible realizar más optimizaciones específicas para x86. – horseyguy

+0

+1, ya que es un buen primer consejo, aunque no se aplique en el caso de barandilla. – peterchen

+1

No estoy seguro de que sea un buen consejo. Una máquina moderna típica tiene aproximadamente el mismo ancho de banda de memoria para la CPU y la GPU. Por ejemplo, las muchas computadoras portátiles populares usan gráficos Intel HD, que usa la misma RAM que la CPU. La CPU ya puede saturar el bus de memoria. Para memcpy, esperaría un rendimiento similar en la CPU o GPU. –

3

Si es específico de los procesadores Intel, puede beneficiarse de IPP. Si sabe que se ejecutará con una GPU Nvidia, quizás pueda usar CUDA; en ambos casos, es mejor parecer más amplio que la optimización de memcpy(); ofrecen oportunidades para mejorar su algoritmo a un nivel superior. Ambos son sin embargo dependientes de hardware específico.

6

En cualquier nivel de optimización de -O1 o superior, GCC utilizarán las definiciones incorporadas para funciones como memcpy - con derecho -march parámetro (-march=pentium4 para el conjunto de características que mencionas) debe generar código en línea bastante óptima de una arquitectura específica.

Me gustaría compararlo y ver lo que sale.

6

El código SSE publicado por hapalibashi es el camino a seguir.

Si necesita aún más rendimiento y no se aparta de la larga y tortuosa ruta de escribir un controlador de dispositivo: todas las plataformas importantes hoy en día tienen un controlador DMA que es capaz de hacer un trabajo de copiado más rápido y en paralelo al código de la CPU podría hacer.

Eso implica escribir un controlador. Ningún gran SO del que sea consciente expone esta funcionalidad al usuario debido a los riesgos de seguridad.

Sin embargo, puede valer la pena (si necesita el rendimiento) ya que ningún código en la tierra podría superar a una pieza de hardware que está diseñada para hacer ese trabajo.

+1

Acabo de publicar una respuesta que habla sobre el ancho de banda de la RAM. Si lo que digo es verdad, entonces no creo que el motor DMA pueda lograr mucho más allá de lo que la CPU puede lograr. ¿Me he perdido algo? –

5

Esta pregunta tiene cuatro años y estoy un poco sorprendido de que nadie haya mencionado el ancho de banda de la memoria todavía. CPU-Z informa que mi máquina tiene PC3-10700 RAM. Que la RAM tiene un ancho de banda máximo (también conocido como tasa de transferencia, rendimiento, etc.) de 10700 MBytes/seg. La CPU en mi máquina es una CPU i5-2430M, con una frecuencia máxima de turbo de 3 GHz.

En teoría, con una CPU infinitamente rápido y mi memoria RAM, memcpy podría ir a 5300 Mbytes/seg, es decir, la mitad de 10700 memcpy porque tiene que leer y luego escribir en la memoria RAM. (Editar: Como v.oddou señaló, esta es una aproximación simplista).

Por otro lado, imagina que tenemos una RAM infinitamente rápida y una CPU realista, ¿qué podríamos lograr? Usemos mi CPU de 3 GHz como ejemplo. Si pudiera hacer una lectura de 32 bits y una de 32 bits, escribir cada ciclo, entonces podría transferir 3e9 * 4 = 12000 MBytes/seg. Esto parece fácilmente al alcance de una CPU moderna. Ya podemos ver que el código que se ejecuta en la CPU no es realmente el cuello de botella. Esta es una de las razones por las que las máquinas modernas tienen cachés de datos.

Podemos medir lo que realmente puede hacer la CPU mediante la evaluación comparativa de memcpy cuando sabemos que los datos están en la memoria caché. Hacer esto con precisión es complicado. Hice una aplicación simple que escribía números aleatorios en una matriz, los remecía a otra matriz y luego sumaba los datos copiados. Pasé por el código en el depurador para asegurarme de que el compilador inteligente no haya eliminado la copia. Alterar el tamaño de la matriz altera el rendimiento de la memoria caché: pequeñas matrices encajan en la memoria caché, y las grandes no tanto. Me dieron los siguientes resultados:

  • 40 KByte matrices: 16000 Mbytes/seg
  • 400 KByte matrices: 11000 Mbytes/seg
  • 4000 matrices KByte: 3100 MBytes/seg

Obviamente, mi CPU puede leer y escribir más de 32 bits por ciclo, ya que 16000 es más que los 12000 que calculé teóricamente. Esto significa que la CPU es aún menos un cuello de botella de lo que pensaba. Utilicé Visual Studio 2005, y entrando en la implementación estándar de memcpy, puedo ver que usa la instrucción movqda en mi máquina. Supongo que esto puede leer y escribir 64 bits por ciclo.

El buen código hapalibashi publicado alcanza 4200 MBytes/seg en mi máquina, aproximadamente un 40% más rápido que la implementación VS 2005. Supongo que es más rápido porque usa la instrucción de captación previa para mejorar el rendimiento de la memoria caché.

En resumen, el código que se ejecuta en la CPU no es el cuello de botella y sintonizar ese código solo hará pequeñas mejoras.

+0

Tu proceso de pensamiento es bueno. Sin embargo, le falta pensar en los números de marketing de la RAM, esto es todas las cifras de cuádruple bombeo, que no corresponde a la velocidad de 1 canal. Y también es la velocidad antes del autobús, también hay gastos generales de gestión en el modelo numa que tienen los Core i7/opterons. –

Cuestiones relacionadas