¿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]);
c++
performance
optimization
user576670
fuente
fuente
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 *)
).std::vector<char>
? O desde que usted dicebits
,std::bitset
?(int*) copyMe->bits[0]
hace?int
tamaño que dicta, pero parece una receta para un desastre definido por la implementación, como tantas otras cosas aquí.(int *)
reparto es solo un comportamiento indefinido puro, no definido por la implementación. Tratar de escribir letras mediante un elenco viola las estrictas reglas de alias y, por lo tanto, no está totalmente definido por el Estándar. (Además, en C ++, aunque no en C, no puede escribir juegos de palabras a través deunion
ninguno de los dos). La única excepción es si está convirtiendo a una variante dechar*
, pero la asignación no es simétrica.Respuestas:
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 fuestd::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 lastd::copy
versión. Mi código aprovecha la captura de datos en la mayor cantidad de fragmentos posible (muchas otras implementaciones operan conchar
/char *
, mientras que opero conT
/T *
(dondeT
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
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
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ó astd::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
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, cuandomemcpy
es legal, nostd::copy
debería funcionar peor. La implementación trivial destd::copy
eso difierememcpy
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 llamastd::copy
, la función mantiene los tipos intactos.memcpy
opera envoid *
, que descarta casi toda la información útil. Por ejemplo, si paso una matriz destd::uint64_t
, el compilador o el implementador de la biblioteca pueden aprovechar la alineación de 64 bitsstd::copy
, pero puede ser más difícil hacerlomemcpy
. 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 lentomemcpy
y a veces más rápido con cualquier compilador de optimización moderno. Además, todo lo que puedasmemcpy
, puedesstd::copy
.memcpy
no permite ninguna superposición en las memorias intermedias, mientras questd::copy
admite la superposición en una dirección (constd::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 usarstd::copy
cuando necesites copiar fragmentos de datos.fuente
std::copy
sea 2.99% o 0.72% o -0.11% más rápido quememcpy
, estos tiempos son para que se ejecute todo el programa. Sin embargo, generalmente siento que los puntos de referencia en código real son más útiles que los puntos de referencia en código falso. Todo mi programa consiguió ese cambio en la velocidad de ejecución. Los efectos reales de solo los dos esquemas de copia tendrán mayores diferencias que las que se muestran aquí cuando se toman de forma aislada, pero esto muestra que pueden tener diferencias medibles en el código real.memcpy
ystd::copy
tiene diferentes implementaciones, por lo que en algunos casos el compilador optimiza el código circundante y el código de copia de memoria real como una pieza integral de código. En otras palabras, a veces uno es mejor que otro e incluso en otras palabras, decidir cuál usar es una optimización prematura o incluso estúpida, porque en cada situación tienes que hacer una nueva investigación y, lo que es más, los programas generalmente se están desarrollando, así que después algunos cambios menores pueden perderse la ventaja de la función sobre otros.std::copy
es una función en línea trivial que solo llamamemcpy
cuando es legal. La alineación básica eliminaría cualquier diferencia de rendimiento negativa. Actualizaré la publicación con una pequeña explicación de por qué std :: copy podría ser más rápido.Todos los compiladores que conozco reemplazarán un simple
std::copy
con amemcpy
cuando sea apropiado, o incluso mejor, vectoricen la copia para que sea aún más rápido que amemcpy
.En cualquier caso: perfil y descúbrelo tú mismo. Diferentes compiladores harán diferentes cosas, y es muy posible que no haga exactamente lo que pides.
Vea esta presentación sobre optimizaciones del compilador (pdf).
Esto es lo que hace GCC para un simple
std::copy
tipo de POD.Aquí está el desmontaje (con solo
-O
optimización), que muestra la llamada amemmove
:Si cambia la firma de la función a
entonces se
memmove
conviertememcpy
en una mejora leve en el rendimiento. Tenga en cuenta quememcpy
sí será fuertemente vectorizado.fuente
memmove
no debería ser más rápido; más bien, debería ser un poco más lento porque debe tener en cuenta la posibilidad de que los dos rangos de datos se superpongan. Creo questd::copy
permite la superposición de datos, por lo que tiene que llamarmemmove
.memcpy
. Me lleva a creer que GCC verifica si hay superposición de memoria.std::copy
permite la superposición en una dirección pero no en la otra. El comienzo de la salida no puede estar dentro del rango de entrada, pero el comienzo de la entrada puede estar dentro del rango de salida. Esto es un poco extraño, porque el orden de las asignaciones está definido, y una llamada puede ser UB aunque el efecto de esas asignaciones, en ese orden, esté definido. Pero supongo que la restricción permite optimizaciones de vectorización.Siempre use
std::copy
porquememcpy
está limitado solo a estructuras POD de estilo C, y el compilador probablemente reemplazará las llamadasstd::copy
conmemcpy
si los objetivos son de hecho POD.Además,
std::copy
se puede usar con muchos tipos de iteradores, no solo punteros.std::copy
es más flexible para no perder rendimiento y es el claro ganador.fuente
std::copy(container.begin(), container.end(), destination);
copiará el contenido decontainer
(todo entrebegin
yend
) en el búfer indicado pordestination
.std::copy
no requiere travesuras como&*container.begin()
o&container.back() + 1
.En teoría,
memcpy
podría tener una ligera , imperceptible , infinitesimal , ventaja de rendimiento, solo porque no tiene los mismos requisitos questd::copy
. De la página del manual dememcpy
:En otras palabras,
memcpy
puede ignorar la posibilidad de superposición de datos. (Pasar matrices superpuestas amemcpy
es un comportamiento indefinido). Pormemcpy
lo tanto, no es necesario verificar explícitamente esta condición, mientras questd::copy
puede usarse siempre que elOutputIterator
parámetro no esté en el rango de origen. Tenga en cuenta que esto no es lo mismo que decir que el rango de origen y el rango de destino no pueden superponerse.Entonces, dado que
std::copy
tiene requisitos algo diferentes, en teoría debería ser un poco (con un énfasis extremo en un poco ) más lento, ya que probablemente comprobará la superposición de los arreglos en C o delegará la copia de los arreglos en Cmemmove
, que debe realizar el cheque. Pero en la práctica, usted (y la mayoría de los perfiladores) probablemente ni siquiera detectarán ninguna diferencia.Por supuesto, si no está trabajando con POD , no puede usarlo de
memcpy
todos modos.fuente
std::copy<char>
. Perostd::copy<int>
puede suponer que sus entradas están alineadas int. Eso hará una diferencia mucho mayor, porque afecta a cada elemento. La superposición es una verificación única.memcpy
He visto comprueban la alineación e intentan copiar palabras en lugar de byte a byte.memcpy
interfaz pierde la información de alineación. Por lo tanto,memcpy
tiene que hacer comprobaciones de alineación en tiempo de ejecución para manejar comienzos y finales no alineados. Esos cheques pueden ser baratos pero no son gratuitos. Mientras questd::copy
puede evitar estos controles y vectorizar. Además, el compilador puede probar que las matrices de origen y destino no se superponen y de nuevo se vectorizan sin que el usuario tenga que elegir entrememcpy
ymemmove
.Mi regla es simple. Si está utilizando C ++, prefiera las bibliotecas de C ++ y no C :)
fuente
std::end(c_arr)
lugar de hacerloc_arr + i_hope_this_is_the_right_number_of elements
es más seguro? y quizás lo más importante, más claro. Y eso sería el punto subrayo en este caso concreto:std::copy()
es más idiomática, más fácil de mantener si los tipos de los iteradores cambios más adelante, conduce a la sintaxis más clara, etcstd::copy
es más seguro porque copia correctamente los datos pasados en caso de que no sean de tipo POD.memcpy
felizmente copiará unstd::string
objeto a una nueva representación byte a byte.Solo una pequeña adición: la diferencia de velocidad entre
memcpy()
ystd::copy()
puede variar bastante dependiendo de si las optimizaciones están habilitadas o deshabilitadas. Con g ++ 6.2.0 y sin optimizacionesmemcpy()
gana claramente:Cuando las optimizaciones están habilitadas (
-O3
), todo se ve casi igual de nuevo:Cuanto más grande es la matriz, menos perceptible es el efecto, pero incluso
N=1000
memcpy()
es aproximadamente el doble de rápido cuando las optimizaciones no están habilitadas.Código fuente (requiere Google Benchmark):
fuente
Si realmente necesita el máximo rendimiento de copia (que quizás no necesite), no use ninguno de ellos .
Hay una gran cantidad que se puede hacer para optimizar la copia de memoria - aún más si usted está dispuesto a utilizar múltiples hilos / núcleos para ello. Ver, por ejemplo:
¿Qué falta / subóptimo en esta implementación de memcpy?
Tanto la pregunta como algunas de las respuestas han sugerido implementaciones o enlaces a implementaciones.
fuente
La creación de perfiles muestra esa declaración:
std::copy()
siempre es tan rápidomemcpy()
o más rápido es falso.Mi sistema:
El código (lenguaje: c ++):
Red Alert señaló que el código usa memcpy de matriz a matriz y std :: copy de matriz a vector. Esa podría ser una razón para una memoria más rápida.
Ya que hay
v.reserve (sizeof (arr1));
no habrá diferencia en la copia al vector o matriz.
El código está arreglado para usar una matriz en ambos casos. memcpy aún más rápido:
fuente
std::copy
de un vector a una matriz de alguna manera hizomemcpy
que casi el doble de tiempo? Estos datos son altamente sospechosos. Compilé su código usando gcc con -O3, y el ensamblado generado es el mismo para ambos bucles. Por lo tanto, cualquier diferencia en el tiempo que observe en su máquina es solo incidental.