2009-07-30 13 views
14

¿Existen alternativas más rápidas a memcpy() en C++?optimizado memcpy

+23

Si hubiera una manera más rápida, ¿por qué no la usarían en la implementación 'memcpy'? –

+0

¿Qué hay de SSE? –

+0

@MehrdadAfshari: La función 'memcpy' se puede invocar con punteros de alineación arbitraria, a cosas de tipo PODS arbitrario, y puede aliar arbitrariamente cualquier objeto PODS cuya dirección haya sido expuesta a código externo. Dado 'struct fnord a, b; void * volátil p = & a, * volátil q = & b; 'Esperaría que' * ((struct fnord *) p) = * ((struct fnord *) q); 'funcione mucho mejor que' memcpy (p, q , sizeof (struct fnord)); 'dado que en el primer caso un compilador podría legítimamente asumir que p y q se alinearán para una' struct fnord' y no alias ninguna otra cosa, pero en el último caso no puede. – supercat

Respuesta

17

Improbable. Su compilador/biblioteca estándar probablemente tendrá una implementación muy eficiente y adaptada de memcpy. Y memcpy es básicamente la aplicación más baja que existe para copiar una parte de la memoria en otra.

Si desea más aceleraciones, encuentre una manera de no necesitar ninguna copia de memoria.

+0

en realidad, hay al menos una alternativa que será más rápida en * algunos * casos, al menos, y nunca debería ser más lenta. Ver mi respuesta :) – jalf

+0

-1: es bien sabido que las funciones integradas de GCC son malas (ver puntos de referencia de Agner Fog). Bueno, tal vez finalmente se ha solucionado, pero ilustra el punto de que la biblioteca * * no * está necesariamente optimizada. –

+0

@Bastien: ¿podría proporcionar un puntero a los puntos de referencia de Agner Fog? Veo que hay mucha información en su sitio sobre la optimización, pero no pude encontrar referencias claras (excepto una tabla que comparaba algunas rutinas memcpy() y strlen(), y hasta donde puedo decir el soporte intrínseco para las rutinas se apagó). –

7

Esta respuesta para una pregunta muy similar (aproximadamente memset()) se aplica también aquí.

Básicamente dice que los compiladores generan un código muy óptimo para memcpy()/memset() - y código diferente dependiendo de la naturaleza de los objetos (tamaño, alineación, etc.).

Y recuerde, solo memcpy() PODs en C++.

1

Dependiendo de lo que intente hacer ... si es una memcpy lo suficientemente grande, y usted solo está escribiendo en la copia escasamente, un mapa de bits con MMAP_PRIVATE para crear un mapeo de copia en escritura podría ser Más rápido.

+0

Sin embargo, esto requiere escribirlo en un archivo en primer lugar ... – bdonlan

+0

Y la copia en escritura solo funcionará si el espacio de direcciones está en un proceso diferente (volví a decir eso.) En realidad, no creo que tengas que escribirlo en un archivo si usas el indicador MAP_ANONYMOUS. – smcameron

+3

no, la asignación de memoria se puede usar también entre dos ubicaciones de memoria – jalf

1

Dependiendo de su plataforma, puede haber casos de uso específicos, como si supiera que el origen y el destino están alineados con una línea de caché y el tamaño es un número entero del tamaño de la línea de caché. En general, la mayoría de los compiladores producirán código bastante óptimo para memcpy.

19

Primero, un consejo. Asuma que las personas que escribieron su biblioteca estándar no son estúpidas. Si hubiera una forma más rápida de implementar una memcpy general, lo habrían hecho.

En segundo lugar, sí, hay mejores alternativas.

  • En C++, utilice la función std::copy. Hace lo mismo, pero es 1) más seguro y 2) potencialmente más rápido en algunos casos. Es una plantilla, lo que significa que puede especializarse para tipos específicos, lo que la hace potencialmente más rápida que la memcpy C general.
  • O bien, puede utilizar su conocimiento superior de su situación específica. Los implementadores de memcpy tuvieron que escribirlo para que funcionase bien en cada caso. Si tiene información específica sobre la situación en que la necesita, es posible que pueda escribir una versión más rápida. Por ejemplo, ¿cuánta memoria necesita copiar? ¿Cómo está alineado? Eso podría permitirle escribir una memcpy más eficiente para este caso específico. Pero no será tan bueno en la mayoría de los otros casos (si funciona)
+7

Es poco probable que el compilador realmente llame a una función memcpy. Sé que en gcc no funciona, pero en realidad reemplaza memcpy con una sola instrucción en i386. –

+1

@PaulBiggar: Para los tipos de POD, la copia estándar de GCC llamará a 'memmove'. Si proporciona pistas de aliasing con '__restrict', entonces llamará a' memcpy'. –

1

No estoy seguro de que el uso de la memcpy predeterminada sea siempre la mejor opción. La mayoría de las implementaciones de memcpy que he analizado tienden a intentar alinear los datos al inicio y luego hacer copias alineadas. Si los datos ya están alineados, o son bastante pequeños, entonces esto está perdiendo tiempo.

A veces es beneficioso tener una copia de palabra especializada, una copia de media palabra, una copia de byte de memcpy, siempre que no tenga un efecto demasiado negativo en las memorias caché.

Además, es posible que desee un mayor control sobre el algoritmo de asignación real.En la industria de los juegos, es excepcionalmente común que las personas escriban sus propias rutinas de asignación de memoria, independientemente de cuánto esfuerzo gastaron los desarrolladores de la herramienta para desarrollarla. Los juegos que he visto casi siempre tienden a usar Doug Lea's Malloc.

Sin embargo, en términos generales, estaría perdiendo el tiempo tratando de optimizar memcpy ya que sin duda habrá muchos bits de código más fáciles en su aplicación para acelerar.

7

El experto en optimización Agner Fog ha publicado las funciones de memoria optimizadas: http://agner.org/optimize/#asmlib. Sin embargo, está bajo GPL.

Hace algún tiempo, Agner dijo que estas funciones deberían reemplazar a las incorporadas por GCC porque son mucho más rápidas. No sé si se ha hecho desde entonces.

2

Para encontrar o escribir una rutina de copia rápida de la memoria, debemos entender cómo funcionan los procesadores.

Los procesadores desde Intel Pentium Pro hacen "ejecución fuera de orden". Pueden ejecutar muchas instrucciones en paralelo si las instrucciones no tienen dependencias. Pero este es solo el caso cuando las instrucciones operan solo con registros. Si operan con memoria, se usan unidades de CPU adicionales, llamadas "unidades de carga" (para leer datos de la memoria) y "unidades de almacenamiento" (para escribir datos en la memoria). La mayoría de las CPU tienen dos unidades de carga y una unidad de tienda, es decir, pueden ejecutar en paralelo dos instrucciones que se leen desde la memoria y una instrucción que se escribe en la memoria (una vez más, si no se afectan entre sí). El tamaño de estas unidades suele ser el mismo que el tamaño máximo de registro: si la CPU tiene registros XMM (SSE), tiene 16 bytes, si tiene registros YMM (AVX), tiene 32 bytes, y así sucesivamente. Todas las instrucciones que leen o escriben en la memoria se traducen en microoperaciones (microoperaciones) que van al conjunto común de microoperaciones y esperan allí para que la carga y las unidades de almacenamiento puedan servirlas. Una sola unidad de carga o almacén solo puede servir una microoperación a la vez, independientemente del tamaño de datos que necesite cargar o almacenar, ya sea 1 byte o 32 bytes.

Por lo tanto, la copia de la memoria más rápida se moverá desde y hacia los registros con el tamaño máximo. Para los procesadores habilitados para AVX, manera más rápida para copiar la memoria sería repetir la secuencia siguiente, loop-desenrolló:

vmovdqa  ymm0,ymmword ptr [rcx] 
vmovdqa  ymm1,ymmword ptr [rcx+20h] 
vmovdqa  ymmword ptr [rdx],ymm0 
vmovdqa  ymmword ptr [rdx+20h],ymm1 

El código de Google publicado anteriormente por hplbsh no es muy bueno, ya que utilizan los 8 registros XMM a mantenga los datos antes de que comiencen a escribirlos, mientras que no es necesario, ya que solo tenemos dos unidades de carga y una unidad de tienda. Entonces solo dos registros dan mejores resultados. Usar tantos registros de ninguna manera mejora el rendimiento.

Una rutina de copia de memoria también puede usar algunas técnicas "avanzadas" como "precapturar" para indicar al procesador que cargue memoria en la memoria caché y "escrituras no temporales" (si está copiando fragmentos de memoria muy grandes y no t necesita que los datos del búfer de salida se lean inmediatamente), alineados frente a escrituras desalineadas, etc.

Los procesadores modernos, lanzados desde 2013, si tienen el bit ERMS en el CPUID, tienen los llamados "mejorados rep movsb" ", Por lo que para la copia de gran tamaño de la memoria, se puede usar" rep movsb "; la copia será muy rápida, incluso más rápida que con los registros ymm, y funcionará correctamente con la memoria caché. Sin embargo, los costos de inicio de esta instrucción son muy altos, unos 35 ciclos, por lo que se paga solo en bloques de memoria grandes.

Espero que ahora sea más fácil para usted elegir o escribir la mejor rutina de copia de memoria necesaria para su caso.

Incluso puede conservar el memcpy/memmove estándar, pero obtenga su propio granmemcpy especial() para sus necesidades.