Voy a ir en contra de la sabiduría general aquí que std::copy
tendrá una pérdida de rendimiento leve, casi imperceptible. Acabo de hacer una prueba y descubrí que no era cierto: noté una diferencia de rendimiento. Sin embargo, el ganador fue std::copy
.
Escribí una implementación de C ++ SHA-2. En mi prueba, combiné 5 cadenas con las cuatro versiones SHA-2 (224, 256, 384, 512) y realicé un bucle 300 veces. Mido los tiempos usando Boost.timer. Ese contador de 300 bucles es suficiente para estabilizar completamente mis resultados. Ejecuté la prueba 5 veces cada uno, alternando entre la memcpy
versión y la std::copy
versión. Mi código aprovecha la captura de datos en la mayor cantidad de fragmentos posible (muchas otras implementaciones operan con char
/ char *
, mientras que 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), por lo que el acceso a la memoria es rápido Los tipos más grandes que puedo son fundamentales 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%
Incremento promedio total en la velocidad de std :: copy over memcpy: 2.99%
Mi compilador es gcc 4.6.3 en Fedora 16 x86_64. Mis banderas de optimización son -Ofast -march=native -funsafe-loop-optimizations
.
Código para mis implementaciones SHA-2.
Decidí ejecutar una prueba en mi implementación 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 variaron enormemente de una ejecución a la siguiente, así que supongo que estaba ocurriendo algún tipo de actividad del sistema operativo. Decidí comenzar de nuevo.
La misma configuración y banderas del compilador. Solo hay una versión de MD5, y es más rápida que SHA-2, así que hice 3000 bucles 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 promedio total en la velocidad de std :: copy over memcpy: 0.11%
Código para mi implementación MD5
Estos resultados sugieren que hay alguna optimización que std :: copy utilizada en mis pruebas SHA-2 que std::copy
no podría 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 parámetro de función.
Hice un poco más de pruebas para ver qué podía hacer para std::copy
acelerar de nuevo. La respuesta resultó ser simple: active 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%
Incremento promedio total en la velocidad de std :: copy over 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é podría std::copy
aumentar el rendimiento?
Primero, no esperaría que fuera más lenta para ninguna implementación, siempre y cuando la optimización de inlining esté activada. Todos los compiladores están en línea agresivamente; posiblemente sea la optimización más importante porque permite muchas otras optimizaciones. std::copy
puedo (y sospecho que todas las implementaciones del mundo real lo hacen) detectar que los argumentos son trivialmente copiables y que la memoria se presenta secuencialmente. Esto significa que en el peor de los casos, cuando memcpy
es legal, no std::copy
debería funcionar peor. La implementación trivial de std::copy
eso difiere memcpy
debe cumplir con los criterios de su compilador de "siempre alinear esto al optimizar la velocidad o el tamaño".
Sin embargo, std::copy
también guarda más de su información. Cuando llama 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 una matriz de std::uint64_t
, el compilador o el implementador de la biblioteca pueden aprovechar la alineación de 64 bits std::copy
, pero puede ser más difícil hacerlo memcpy
. Muchas implementaciones de algoritmos como este funcionan trabajando primero en la porción no alineada al comienzo del rango, luego la porción alineada, luego la porción no alineada al final. Si se garantiza que todo está alineado, entonces el código se vuelve más simple y rápido, y más fácil para que el predictor de rama en su procesador sea correcto.
Optimización prematura?
std::copy
Está en una posición interesante. Espero que nunca sea más lento memcpy
y a veces más rápido con cualquier compilador de optimización moderno. Además, todo lo que puedas memcpy
, puedes std::copy
. memcpy
no permite ninguna superposición en las memorias intermedias, mientras que std::copy
admite la superposición en una dirección (con std::copy_backward
la otra dirección de superposición). memcpy
solo funciona con punteros, std::copy
funciona en cualquier iteradores ( std::map
, std::vector
, std::deque
, o mi propio tipo personalizado). En otras palabras, solo debes usar std::copy
cuando necesites copiar fragmentos de datos.
char
se puede firmar o no, según la implementación. Si el número de bytes puede ser> = 128, utilícelounsigned char
para sus conjuntos de bytes. (El(int *)
elenco también sería más seguro(unsigned int *)
).