memcat example c performance memcpy

example - memcat c



una alternativa más rápida a memcpy? (16)

A veces, las funciones como memcpy, memset, ... se implementan de dos maneras diferentes:

  • una vez como una función real
  • una vez como un conjunto que está inmediatamente en línea

No todos los compiladores toman la versión de montaje en línea de forma predeterminada, su compilador puede usar la variante de función de manera predeterminada, lo que ocasiona cierta sobrecarga debido a la llamada a la función. Compruebe su compilador para ver cómo tomar la variante intrínseca de la función (opción de línea de comando, pragma, ...).

Editar: consulte http://msdn.microsoft.com/en-us/library/tzkfha43%28VS.80%29.aspx para obtener una explicación de los intrínsecos en el compilador de Microsoft C.

Tengo una función que está haciendo memcpy, pero está tomando una cantidad enorme de ciclos. ¿Existe una alternativa / enfoque más rápido que el uso de memcpy para mover un trozo de memoria?



Aquí hay una versión C alternativa de memcpy que es inlineable y me parece que supera a memcpy para GCC para Arm64 en aproximadamente un 50% en la aplicación para la que lo utilicé. Es plataforma independiente de 64 bits. El procesamiento de cola se puede eliminar si la instancia de uso no lo necesita para un poco más de velocidad. Copia matrices uint32_t, tipos de datos más pequeños no probados pero que podrían funcionar. Podría ser capaz de adaptarse a otros tipos de datos. Copia de 64 bits (dos índices se copian simultáneamente). 32 bits también debería funcionar, pero más lento. Créditos al proyecto Neoscrypt.

static inline void newmemcpy(void *__restrict__ dstp, void *__restrict__ srcp, uint len) { ulong *dst = (ulong *) dstp; ulong *src = (ulong *) srcp; uint i, tail; for(i = 0; i < (len / sizeof(ulong)); i++) *dst++ = *src++; /* Remove below if your application does not need it. If console application, you can uncomment the printf to test whether tail processing is being used. */ tail = len & (sizeof(ulong) - 1); if(tail) { //printf("tailused/n"); uchar *dstb = (uchar *) dstp; uchar *srcb = (uchar *) srcp; for(i = len - tail; i < len; i++) dstb[i] = srcb[i]; } }


Debe verificar el código de ensamblaje generado para su código. Lo que no quiere es hacer que la llamada memcpy genere una llamada a la función memcpy en la biblioteca estándar; lo que quiere es tener una llamada repetida a la mejor instrucción ASM para copiar la mayor cantidad de datos, algo así como rep movsq .

¿Cómo puedes lograr esto? Bueno, el compilador optimiza las llamadas a memcpy reemplazándolas con memcpy simples, siempre que sepa la cantidad de datos que debe copiar. Puede ver esto si escribe una memcpy con un valor bien determinado ( constexpr ). Si el compilador no conoce el valor, deberá recurrir a la implementación a nivel de memcpy de memcpy ; el problema es que memcpy debe respetar la granularidad de un byte. Todavía moverá 128 bits a la vez, pero después de cada 128b tendrá que verificar si tiene suficientes datos para copiar como 128b o tiene que volver a 64bits, luego a 32 y 8 (creo que 16 podría ser un valor inferior al óptimo) de todos modos, pero no estoy seguro).

Entonces, lo que quiere es poder decirle a memcpy cuál es el tamaño de sus datos con expresiones const que el compilador puede optimizar. De esta forma no se realiza ninguna llamada a memcpy . Lo que no quiere es pasarle a memcpy una variable que solo se conocerá en tiempo de ejecución. Eso se traduce en una llamada a función y toneladas de pruebas para verificar la mejor instrucción de copia. A veces, un bucle for simple es mejor que memcpy por este motivo (eliminando una llamada de función). Y lo que realmente no quieres es pasar a memcpy un número impar de bytes para copiar.


En general, es más rápido no hacer una copia en absoluto. Si puede adaptar su función para no copiar, no lo sé, pero vale la pena buscarlo.


En realidad, memcpy NO es la manera más rápida, especialmente si la llamas muchas veces. También tenía un código que realmente necesitaba acelerar, y memcpy es lento porque tiene demasiadas verificaciones innecesarias. Por ejemplo, comprueba si los bloques de destino y de memoria de origen se superponen y si debería comenzar a copiarse desde la parte posterior del bloque en lugar de desde el frente. Si no le importan estas consideraciones, definitivamente puede hacerlo mucho mejor. Tengo un código, pero esta es quizás una versión cada vez mejor:

Memcpy muy rápido para el procesamiento de imágenes? .

Si busca, puede encontrar otras implementaciones también. Pero para una verdadera velocidad, necesitas una versión de ensamblaje.



Esta es una respuesta para x86_64 con el conjunto de instrucciones AVX2 presente. Aunque algo similar puede aplicarse para ARM / AArch64 con SIMD.

En Ryzen 1800X con un solo canal de memoria lleno (2 ranuras, 16 GB de DDR4 en cada uno), el siguiente código es 1.56 veces más rápido que memcpy() en el compilador MSVC ++ 2017. Si llena ambos canales de memoria con 2 módulos DDR4, es decir, tiene las 4 ranuras DDR4 ocupadas, es posible que obtenga 2 copias de memoria más rápidas. Para sistemas de memoria de triple (cuádruple) canal, puede obtener 1.5 (2.0) veces más tiempo de copia de memoria si el código se extiende al código AVX512 análogo. Con los sistemas de canal triple / cuádruple AVX2 con todas las ranuras ocupadas no se espera que sean más rápidos porque para cargarlos completamente necesita cargar / almacenar más de 32 bytes a la vez (48 bytes para triple y 64 bytes para cuádruple canal sistemas), mientras que AVX2 puede cargar / almacenar no más de 32 bytes a la vez. Aunque el subprocesamiento múltiple en algunos sistemas puede aliviar esto sin AVX512 o incluso AVX2.

Así que aquí está el código de copia que supone que está copiando un gran bloque de memoria cuyo tamaño es un múltiplo de 32 y el bloque está alineado a 32 bytes.

Para bloques de tamaño no múltiple y bloques no alineados, se puede escribir el código de prólogo / epílogo reduciendo el ancho a 16 (SSE4.1), 8, 4, 2 y finalmente 1 byte a la vez para la cabecera y la cola del bloque. También en el medio se puede usar una matriz local de valores de 2-3 __m256i como un proxy entre las lecturas alineadas de la fuente y las escrituras alineadas en el destino.

#include <immintrin.h> #include <cstdint> /* ... */ void fastMemcpy(void *pvDest, void *pvSrc, size_t nBytes) { assert(nBytes % 32 == 0); assert((intptr_t(pvDest) & 31) == 0); assert((intptr_t(pvSrc) & 31) == 0); const __m256i *pSrc = reinterpret_cast<const __m256i*>(pvSrc); __m256i *pDest = reinterpret_cast<__m256i*>(pvDest); int64_t nVects = nBytes / sizeof(*pSrc); for (; nVects > 0; nVects--, pSrc++, pDest++) { const __m256i loaded = _mm256_stream_load_si256(pSrc); _mm256_stream_si256(pDest, loaded); } _mm_sfence(); }

Una característica clave de este código es que omite la caché de la CPU al copiar: cuando está involucrada la caché de la CPU (es decir, las instrucciones AVX sin _stream_ se usan), la velocidad de copiado cae varias veces en mi sistema.

Mi memoria DDR4 es 2.6GHz CL13. Entonces al copiar 8GB de datos de una matriz a otra obtuve las siguientes velocidades:

memcpy(): 17 208 004 271 bytes/sec. Stream copy: 26 842 874 528 bytes/sec.

Tenga en cuenta que en estas mediciones, el tamaño total de los búferes de entrada y salida se divide por el número de segundos transcurridos. Porque para cada byte de la matriz hay 2 accesos de memoria: uno para leer el byte de la matriz de entrada, y otro para escribir el byte en la matriz de salida. En otras palabras, cuando se copian 8 GB de una matriz a otra, se realizan 16GB de operaciones de acceso a la memoria.

Moderado multihilo puede mejorar aún más el rendimiento alrededor de 1,44 veces, por lo que el aumento total sobre memcpy() llega a 2,55 veces en mi máquina. Así es cómo el rendimiento de copia de secuencias depende de la cantidad de subprocesos utilizados en mi máquina:

Stream copy 1 threads: 27114820909.821 bytes/sec Stream copy 2 threads: 37093291383.193 bytes/sec Stream copy 3 threads: 39133652655.437 bytes/sec Stream copy 4 threads: 39087442742.603 bytes/sec Stream copy 5 threads: 39184708231.360 bytes/sec Stream copy 6 threads: 38294071248.022 bytes/sec Stream copy 7 threads: 38015877356.925 bytes/sec Stream copy 8 threads: 38049387471.070 bytes/sec Stream copy 9 threads: 38044753158.979 bytes/sec Stream copy 10 threads: 37261031309.915 bytes/sec Stream copy 11 threads: 35868511432.914 bytes/sec Stream copy 12 threads: 36124795895.452 bytes/sec Stream copy 13 threads: 36321153287.851 bytes/sec Stream copy 14 threads: 36211294266.431 bytes/sec Stream copy 15 threads: 35032645421.251 bytes/sec Stream copy 16 threads: 33590712593.876 bytes/sec

El código es:

void AsyncStreamCopy(__m256i *pDest, const __m256i *pSrc, int64_t nVects) { for (; nVects > 0; nVects--, pSrc++, pDest++) { const __m256i loaded = _mm256_stream_load_si256(pSrc); _mm256_stream_si256(pDest, loaded); } } void BenchmarkMultithreadStreamCopy(double *gpdOutput, const double *gpdInput, const int64_t cnDoubles) { assert((cnDoubles * sizeof(double)) % sizeof(__m256i) == 0); const uint32_t maxThreads = std::thread::hardware_concurrency(); std::vector<std::thread> thrs; thrs.reserve(maxThreads + 1); const __m256i *pSrc = reinterpret_cast<const __m256i*>(gpdInput); __m256i *pDest = reinterpret_cast<__m256i*>(gpdOutput); const int64_t nVects = cnDoubles * sizeof(*gpdInput) / sizeof(*pSrc); for (uint32_t nThreads = 1; nThreads <= maxThreads; nThreads++) { auto start = std::chrono::high_resolution_clock::now(); lldiv_t perWorker = div((long long)nVects, (long long)nThreads); int64_t nextStart = 0; for (uint32_t i = 0; i < nThreads; i++) { const int64_t curStart = nextStart; nextStart += perWorker.quot; if ((long long)i < perWorker.rem) { nextStart++; } thrs.emplace_back(AsyncStreamCopy, pDest + curStart, pSrc+curStart, nextStart-curStart); } for (uint32_t i = 0; i < nThreads; i++) { thrs[i].join(); } _mm_sfence(); auto elapsed = std::chrono::high_resolution_clock::now() - start; double nSec = 1e-6 * std::chrono::duration_cast<std::chrono::microseconds>(elapsed).count(); printf("Stream copy %d threads: %.3lf bytes/sec/n", (int)nThreads, cnDoubles * 2 * sizeof(double) / nSec); thrs.clear(); } }


Por favor, ofrécenos más detalles. En la arquitectura i386, es muy posible que memcpy sea la manera más rápida de copiar. Pero en una arquitectura diferente para la que el compilador no tiene una versión optimizada, es mejor que reescriba su función memcpy. Hice esto en una arquitectura ARM personalizada usando lenguaje ensamblador. Si transfiere GRANDES trozos de memoria, entonces DMA es probablemente la respuesta que está buscando.

Ofrezca más detalles: arquitectura, sistema operativo (si corresponde).


Por lo general, la biblioteca estándar incluida con el compilador implementará memcpy() la forma más rápida posible para la plataforma de destino.


Si su plataforma lo admite, investigue si puede usar la llamada al sistema mmap () para dejar sus datos en el archivo ... generalmente, el sistema operativo puede manejarlo mejor. Y, como todos han estado diciendo, evite copiar si es posible; los punteros son su amigo en casos como este.


Supongo que debe tener grandes áreas de memoria que desea copiar, si el rendimiento de memcpy se ha convertido en un problema para usted?

En este caso, estaría de acuerdo con la sugerencia de nos de encontrar una forma de NO copiar nada.

En lugar de tener que copiar una gran cantidad de memoria cada vez que necesite cambiarla, probablemente deba probar algunas estructuras de datos alternativas.

Sin saber realmente nada acerca de su área problemática, le sugiero que eche un vistazo a las estructuras de datos persistentes y que implemente una propia o reutilice una implementación existente.


Verifique su compilador / manual de plataforma. Para algunos microprocesadores y kits DSP que usan memcpy es mucho más lento que las funciones intrínsecas o las operaciones DMA .


la memoria a la memoria generalmente se admite en el conjunto de comandos de la CPU, y memcpy generalmente lo usará. Y esta suele ser la forma más rápida.

Debería verificar qué está haciendo exactamente su CPU. En Linux, observe la eficacia de la memoria virtual y el swapi con sar -B 1 o vmstat 1 o mirando en / proc / memstat. Puede ver que su copia tiene que sacar muchas páginas para liberar espacio, o leerlas, etc.

Eso significaría que su problema no está en lo que usa para la copia, sino en cómo usa su sistema la memoria. Es posible que necesite disminuir la caché de archivos o comenzar a escribir antes, o bloquear las páginas en la memoria, etc.


nos tiene razón, lo estás llamando demasiado.

Para ver de dónde lo está llamando y por qué, simplemente deténgalo un par de veces debajo del depurador y mire la pila.


Es probable que memcpy sea ​​la forma más rápida de copiar bytes en la memoria. Si necesita algo más rápido, intente encontrar la manera de no copiar cosas, por ejemplo, intercambie solo punteros, no los datos en sí.