c++ performance optimization

c++ - memcpy arduino



¿Es mejor usar std:: memcpy() o std:: copy() en términos de rendimiento? (8)

El perfil muestra esa instrucción: std::copy() siempre es tan rápido como memcpy() o más rápido es falso.

Mi sistema:

HP-Compaq-dx7500-Microtower 3.13.0-24-generic # 47-Ubuntu SMP vie 2 de mayo 23:30:00 UTC 2014 x86_64 x86_64 x86_64 GNU / Linux.

gcc (Ubuntu 4.8.2-19ubuntu1) 4.8.2

El código (lenguaje: c ++):

const uint32_t arr_size = (1080 * 720 * 3); //HD image in rgb24 const uint32_t iterations = 100000; uint8_t arr1[arr_size]; uint8_t arr2[arr_size]; std::vector<uint8_t> v; main(){ { DPROFILE; memcpy(arr1, arr2, sizeof(arr1)); printf("memcpy()/n"); } v.reserve(sizeof(arr1)); { DPROFILE; std::copy(arr1, arr1 + sizeof(arr1), v.begin()); printf("std::copy()/n"); } { time_t t = time(NULL); for(uint32_t i = 0; i < iterations; ++i) memcpy(arr1, arr2, sizeof(arr1)); printf("memcpy() elapsed %d s/n", time(NULL) - t); } { time_t t = time(NULL); for(uint32_t i = 0; i < iterations; ++i) std::copy(arr1, arr1 + sizeof(arr1), v.begin()); printf("std::copy() elapsed %d s/n", time(NULL) - t); } }

g ++ -O0 -o test_stdcopy test_stdcopy.cpp

memcpy () perfil: main: 21: ahora: 1422969084: 04859 transcurrido: 2650 us
std :: copy () perfil: main: 27: ahora: 1422969084: 04862 transcurrido: 2745 us
memcpy () transcurrido 44 s std :: copy () transcurrido 45 s

g ++ -O3 -o test_stdcopy test_stdcopy.cpp

memcpy () perfil: main: 21: ahora: 1422969601: 04939 transcurrido: 2385 us
std :: copy () perfil: main: 28: ahora: 1422969601: 04941 transcurrido: 2690 us
memcpy () transcurrido 27 s std :: copy () transcurrido 43 s

Red Alert señaló que el código usa memcpy de array a array y std :: copy de array a vector. Esa podría ser una razón para memcpy más rápido.

Como hay

v.reserve (sizeof (arr1));

no habrá diferencia en copiar a vector o matriz.

El código está arreglado para usar una matriz para ambos casos. memcpy aún más rápido:

{ time_t t = time(NULL); for(uint32_t i = 0; i < iterations; ++i) memcpy(arr1, arr2, sizeof(arr1)); printf("memcpy() elapsed %ld s/n", time(NULL) - t); } { time_t t = time(NULL); for(uint32_t i = 0; i < iterations; ++i) std::copy(arr1, arr1 + sizeof(arr1), arr2); printf("std::copy() elapsed %ld s/n", time(NULL) - t); } memcpy() elapsed 44 s std::copy() elapsed 48 s

¿Es mejor usar memcpy como se muestra a continuación o es mejor usar std::copy() en términos de rendimiento? ¿Por qué?

char *bits = NULL; ... bits = new (std::nothrow) char[((int *) copyMe->bits)[0]]; if (bits == NULL) { cout << "ERROR Not enough memory./n"; exit(1); } memcpy (bits, copyMe->bits, ((int *) copyMe->bits)[0]);


En teoría, memcpy podría tener una ventaja de rendimiento leve , imperceptible e infinitesimal , solo porque no tiene los mismos requisitos que std::copy . Desde la página man de memcpy :

Para evitar desbordamientos, el tamaño de las matrices apuntadas por los parámetros fuente y de destino debe ser de al menos un número de bytes y no debe superponerse (para la superposición de bloques de memoria, memmove es un enfoque más seguro).

En otras palabras, memcpy puede ignorar la posibilidad de superposición de datos. (El paso de arreglos superpuestos a memcpy es un comportamiento indefinido). Por memcpy tanto, memcpy no necesita verificar explícitamente esta condición, mientras que std::copy se puede usar siempre que el parámetro OutputIterator no esté en el rango fuente. Tenga en cuenta que esto no es lo mismo que decir que el rango de origen y el rango de destino no se pueden superponer.

Entonces, como std::copy tiene requisitos algo diferentes, en teoría debería ser un poco más lento (con un énfasis extremo ligeramente ), ya que probablemente verifique si se superponen las matrices C, o bien delegue la copia de las C-arrays en memmove , que necesita para realizar el control. Pero en la práctica, usted (y la mayoría de los perfiladores) probablemente no detectarán ninguna diferencia.

Por supuesto, si no estás trabajando con PODs , no puedes usar memcpy todos modos.


Mi regla es simple. Si está usando C ++, prefiera las librerías C ++ y no C :)


Si realmente necesita el máximo rendimiento de copiado (que puede que no sea así), no use ninguno de ellos .

Se pueden hacer muchas cosas para optimizar la copia de memoria, incluso más si está dispuesto a usar múltiples hilos / núcleos para ello. Ver, por ejemplo:

¿Qué falta / no es óptimo en esta implementación de memcpy?

tanto la pregunta como algunas de las respuestas han sugerido implementaciones o enlaces a implementaciones.


Siempre use std::copy porque memcpy está limitado solo a las estructuras POD de estilo C, y es probable que el compilador reemplace las llamadas a std::copy con memcpy si los objetivos son de hecho POD.

Además, std::copy se puede usar con muchos tipos de iteradores, no solo con punteros. std::copy es más flexible sin pérdida de rendimiento y es el claro ganador.


Solo una pequeña adición: la diferencia de velocidad entre memcpy() y std::copy() puede variar bastante dependiendo de si las optimizaciones están habilitadas o deshabilitadas. Con g ++ 6.2.0 y sin optimizaciones, memcpy() gana claramente:

Benchmark Time CPU Iterations --------------------------------------------------- bm_memcpy 17 ns 17 ns 40867738 bm_stdcopy 62 ns 62 ns 11176219 bm_stdcopy_n 72 ns 72 ns 9481749

Cuando las optimizaciones están habilitadas ( -O3 ), todo se ve más o menos igual:

Benchmark Time CPU Iterations --------------------------------------------------- bm_memcpy 3 ns 3 ns 274527617 bm_stdcopy 3 ns 3 ns 272663990 bm_stdcopy_n 3 ns 3 ns 274732792

Cuanto más grande sea la matriz, menos notará el efecto, pero incluso en N=1000 memcpy() es aproximadamente el doble de rápido cuando las optimizaciones no están habilitadas.

Código fuente (requiere Google Benchmark):

#include <string.h> #include <algorithm> #include <vector> #include <benchmark/benchmark.h> constexpr int N = 10; void bm_memcpy(benchmark::State& state) { std::vector<int> a(N); std::vector<int> r(N); while (state.KeepRunning()) { memcpy(r.data(), a.data(), N * sizeof(int)); } } void bm_stdcopy(benchmark::State& state) { std::vector<int> a(N); std::vector<int> r(N); while (state.KeepRunning()) { std::copy(a.begin(), a.end(), r.begin()); } } void bm_stdcopy_n(benchmark::State& state) { std::vector<int> a(N); std::vector<int> r(N); while (state.KeepRunning()) { std::copy_n(a.begin(), N, r.begin()); } } BENCHMARK(bm_memcpy); BENCHMARK(bm_stdcopy); BENCHMARK(bm_stdcopy_n); BENCHMARK_MAIN() /* EOF */


Todos los compiladores que sé reemplazarán una simple std::copy memcpy con una memcpy cuando sea apropiado, o incluso mejor, vectorice la copia para que sea incluso más rápido que una memcpy .

En cualquier caso: perfil y descúbrelo. Los diferentes compiladores harán cosas diferentes, y es muy posible que no haga exactamente lo que preguntas.

Vea esta presentación sobre optimizaciones del compilador (pdf).

Esto es lo que hace GCC para una simple std::copy de un tipo de POD.

#include <algorithm> struct foo { int x, y; }; void bar(foo* a, foo* b, size_t n) { std::copy(a, a + n, b); }

Aquí está el desmontaje (con solo -O optimización), que muestra la llamada a memmove :

bar(foo*, foo*, unsigned long): salq $3, %rdx sarq $3, %rdx testq %rdx, %rdx je .L5 subq $8, %rsp movq %rsi, %rax salq $3, %rdx movq %rdi, %rsi movq %rax, %rdi call memmove addq $8, %rsp .L5: rep ret

Si cambia la firma de la función a

void bar(foo* __restrict a, foo* __restrict b, size_t n)

luego el memmove convierte en una memcpy para una ligera mejora en el rendimiento. Tenga en cuenta que memcpy sí será fuertemente vectorizado.


Voy a ir contra la sabiduría general de que std::copy tendrá una pérdida de rendimiento ligera, casi imperceptible. Acabo de hacer una prueba y descubrí que no era cierta: noté una diferencia en el rendimiento. Sin embargo, el ganador fue std::copy .

Escribí una implementación de C ++ SHA-2. En mi prueba, tengo 5 cadenas usando las cuatro versiones de SHA-2 (224, 256, 384, 512) y bucle 300 veces. Mido los tiempos usando Boost.timer. Ese contador de 300 bucles es suficiente para estabilizar por completo mis resultados. memcpy la prueba 5 veces cada una, alternando entre la versión memcpy y la versión std::copy . Mi código aprovecha la captura de datos en la mayor cantidad de fragmentos posible (muchas otras implementaciones operan con char / char * , mientras que yo opero con T / T * (donde T es el tipo más grande en la implementación del usuario que tiene un comportamiento de desbordamiento correcto) , el rápido acceso a la memoria en los tipos más grandes que puedo es fundamental para el rendimiento de mi algoritmo. Estos son mis resultados:

Tiempo (en segundos) para completar la ejecución de las pruebas SHA-2

std::copy memcpy % increase 6.11 6.29 2.86% 6.09 6.28 3.03% 6.10 6.29 3.02% 6.08 6.27 3.03% 6.08 6.27 3.03%

Aumento medio total en la velocidad de std :: copia sobre memcpy: 2.99%

Mi compilador es gcc 4.6.3 en Fedora 16 x86_64. Mis indicadores de optimización son -Ofast -march=native -funsafe-loop-optimizations .

Código para mis implementaciones SHA-2.

Decidí realizar una prueba en mi implementación de MD5 también. Los resultados fueron mucho menos estables, así que decidí hacer 10 carreras. Sin embargo, después de mis primeros intentos, obtuve resultados que variaban enormemente de una carrera a otra, así que supongo que estaba ocurriendo algún tipo de actividad de sistema operativo. Decidí comenzar de nuevo.

Misma configuración de compilador y banderas. Solo hay una versión de MD5, y es más rápida que SHA-2, así que hice 3000 loops en un conjunto similar de 5 cadenas de prueba.

Estos son mis 10 resultados finales:

Tiempo (en segundos) para completar la ejecución de las pruebas MD5

std::copy memcpy % difference 5.52 5.56 +0.72% 5.56 5.55 -0.18% 5.57 5.53 -0.72% 5.57 5.52 -0.91% 5.56 5.57 +0.18% 5.56 5.57 +0.18% 5.56 5.53 -0.54% 5.53 5.57 +0.72% 5.59 5.57 -0.36% 5.57 5.56 -0.18%

Disminución media total en la velocidad de std :: copia sobre memcpy: 0.11%

Código para mi implementación de MD5

Estos resultados sugieren que hay alguna optimización que std :: copy usó en mis pruebas SHA-2 que std::copy no pudo usar en mis pruebas MD5. En las pruebas SHA-2, ambas matrices se crearon en la misma función que llamó a std::copy / memcpy . En mis pruebas MD5, una de las matrices se pasó a la función como un parámetro de función.

Hice un poco más de prueba para ver qué podía hacer para volver a hacer std::copy más rápido. La respuesta resultó ser simple: activar la optimización del tiempo de enlace. Estos son mis resultados con LTO activado (opción -flto en gcc):

Tiempo (en segundos) para completar la ejecución de las pruebas MD5 con -flto

std::copy memcpy % difference 5.54 5.57 +0.54% 5.50 5.53 +0.54% 5.54 5.58 +0.72% 5.50 5.57 +1.26% 5.54 5.58 +0.72% 5.54 5.57 +0.54% 5.54 5.56 +0.36% 5.54 5.58 +0.72% 5.51 5.58 +1.25% 5.54 5.57 +0.54%

Aumento medio total en la velocidad de std :: copia sobre memcpy: 0.72%

En resumen, no parece haber una penalización de rendimiento por usar std::copy . De hecho, parece haber una ganancia de rendimiento.

Explicación de resultados

Entonces, ¿por qué std::copy dar un impulso al rendimiento?

En primer lugar, no esperaría que fuera más lento para cualquier implementación, siempre que la optimización de la línea esté activada. Todos los compiladores se alinean agresivamente; es posiblemente la optimización más importante porque permite tantas otras optimizaciones. std::copy puede (y sospecho que todas las implementaciones del mundo real lo hacen) detectar que los argumentos son triviales y que la memoria se distribuye secuencialmente. Esto significa que en el peor de los casos, cuando memcpy es legal, std::copy debería funcionar peor. La implementación trivial de std::copy que difiere a memcpy debe cumplir con los criterios de su compilador de " memcpy siempre esto al optimizar velocidad o tamaño".

Sin embargo, std::copy también conserva más información. Cuando llama a std::copy , la función mantiene los tipos intactos. memcpy opera en void * , que descarta casi toda la información útil. Por ejemplo, si paso en una matriz de std::uint64_t , el compilador o el implementador de la biblioteca puede aprovechar la alineación de 64 bits con std::copy , pero puede ser más difícil hacerlo con memcpy . Muchas implementaciones de algoritmos como este funcionan trabajando primero en la parte desalineada al comienzo del rango, luego en la parte alineada y luego en la parte desalineada al final. Si todo está garantizado para estar alineado, entonces el código se vuelve más simple y más rápido, y más fácil para que el predictor de bifurcación en su procesador sea correcto.

Optimización prematura?

std::copy está en una posición interesante. Espero que nunca sea más lento que memcpy y, a veces más rápido con cualquier compilador de optimización moderna. Además, cualquier cosa que pueda memcpy , puede std::copy . memcpy no permite superposición en los búferes, mientras que std::copy admite solapamiento en una dirección (con std::copy_backward para la otra dirección de solapamiento). memcpy solo funciona en punteros, std::copy funciona en cualquier iterador ( std::map , std::vector , std::deque o mi propio tipo personalizado). En otras palabras, solo debe usar std::copy cuando necesite copiar fragmentos de datos.