c++ - funciones - memset
¿En qué casos debo usar memcpy sobre los operadores estándar en C++? (7)
La eficiencia no debe ser tu problema.
Escribe un código limpio y fácil de mantener.
Me molesta que tantas respuestas indiquen que memcpy () es ineficiente. Está diseñado para ser la forma más eficiente de copiar bloques de memoria (para programas C).
Así que escribí lo siguiente como una prueba:
#include <algorithm>
extern float a[3];
extern float b[3];
extern void base();
int main()
{
base();
#if defined(M1)
a[0] = b[0];
a[1] = b[1];
a[2] = b[2];
#elif defined(M2)
memcpy(a, b, 3*sizeof(float));
#elif defined(M3)
std::copy(&a[0], &a[3], &b[0]);
#endif
base();
}
Entonces, para comparar, el código produce:
g++ -O3 -S xr.cpp -o s0.s
g++ -O3 -S xr.cpp -o s1.s -DM1
g++ -O3 -S xr.cpp -o s2.s -DM2
g++ -O3 -S xr.cpp -o s3.s -DM3
echo "=======" > D
diff s0.s s1.s >> D
echo "=======" >> D
diff s0.s s2.s >> D
echo "=======" >> D
diff s0.s s3.s >> D
Esto dio como resultado: (comentarios agregados a mano)
======= // Copy by hand
10a11,18
> movq _a@GOTPCREL(%rip), %rcx
> movq _b@GOTPCREL(%rip), %rdx
> movl (%rdx), %eax
> movl %eax, (%rcx)
> movl 4(%rdx), %eax
> movl %eax, 4(%rcx)
> movl 8(%rdx), %eax
> movl %eax, 8(%rcx)
======= // memcpy()
10a11,16
> movq _a@GOTPCREL(%rip), %rcx
> movq _b@GOTPCREL(%rip), %rdx
> movq (%rdx), %rax
> movq %rax, (%rcx)
> movl 8(%rdx), %eax
> movl %eax, 8(%rcx)
======= // std::copy()
10a11,14
> movq _a@GOTPCREL(%rip), %rsi
> movl $12, %edx
> movq _b@GOTPCREL(%rip), %rdi
> call _memmove
Se agregaron los resultados de tiempo para ejecutar lo anterior dentro de un bucle de 1000000000
.
g++ -c -O3 -DM1 X.cpp
g++ -O3 X.o base.o -o m1
g++ -c -O3 -DM2 X.cpp
g++ -O3 X.o base.o -o m2
g++ -c -O3 -DM3 X.cpp
g++ -O3 X.o base.o -o m3
time ./m1
real 0m2.486s
user 0m2.478s
sys 0m0.005s
time ./m2
real 0m1.859s
user 0m1.853s
sys 0m0.004s
time ./m3
real 0m1.858s
user 0m1.851s
sys 0m0.006s
¿Cuándo puedo obtener un mejor rendimiento con memcpy
o cómo me beneficio al usarlo? Por ejemplo:
float a[3]; float b[3];
es código:
memcpy(a, b, 3*sizeof(float));
más rápido que este?
a[0] = b[0];
a[1] = b[1];
a[2] = b[2];
Los beneficios de memcpy? Probablemente legibilidad. De lo contrario, tendrías que hacer una serie de asignaciones o tener un bucle for para copiar, ninguno de los cuales es tan simple y claro como hacer memcpy (por supuesto, siempre y cuando tus tipos sean simples y no requieran construcción / destrucción).
Además, memcpy generalmente está relativamente optimizado para plataformas específicas, hasta el punto de que no será mucho más lento que la asignación simple, y puede incluso ser más rápido.
Los compiladores optimizan específicamente las llamadas memcpy
, al menos clang & gcc lo hace. Entonces deberías preferirlo donde sea que puedas.
No opte por micro optimizaciones prematuras como usar memcpy como este. El uso de la asignación es más claro y menos propenso a errores, y cualquier compilador decente generará un código adecuadamente eficiente. Si, y solo si, ha perfilado el código y encontrado que las asignaciones son un cuello de botella significativo, entonces puede considerar algún tipo de microoptimización, pero en general siempre debe escribir un código claro y sólido en primera instancia.
Puede usar memcpy
solo si los objetos que está copiando no tienen constructores explícitos, así como sus miembros (el llamado POD, "Datos antiguos simples"). Entonces está bien llamar a memcpy
para float
, pero es incorrecto para, por ejemplo, std::string
.
Pero parte del trabajo ya ha sido hecho para usted: std::copy
from <algorithm>
está especializado para tipos incorporados (y posiblemente para cualquier otro tipo de POD - depende de la implementación de STL). Así que escribir std::copy(a, a + 3, b)
es tan rápido (después de la optimización del compilador) como memcpy
, pero es menos propenso a errores.
Supuestamente, como dijo Nawaz, la versión de asignación debería ser más rápida en la mayoría de las plataformas. Esto se debe a que memcpy()
copiará byte por byte, mientras que la segunda versión podría copiar 4 bytes a la vez.
Como siempre es el caso, siempre debe perfilar las aplicaciones para asegurarse de que lo que espera ser el cuello de botella coincida con la realidad.
Editar
Lo mismo se aplica a la matriz dinámica. Como mencionas C ++, deberías usar el algoritmo std::copy()
en ese caso.
Editar
Esta es la salida de código para Windows XP con GCC 4.5.0, compilada con el indicador -O3:
extern "C" void cpy(float* d, float* s, size_t n)
{
memcpy(d, s, sizeof(float)*n);
}
He hecho esta función porque OP especificó matrices dinámicas también.
El ensamblaje de salida es el siguiente:
_cpy:
LFB393:
pushl %ebp
LCFI0:
movl %esp, %ebp
LCFI1:
pushl %edi
LCFI2:
pushl %esi
LCFI3:
movl 8(%ebp), %eax
movl 12(%ebp), %esi
movl 16(%ebp), %ecx
sall $2, %ecx
movl %eax, %edi
rep movsb
popl %esi
LCFI4:
popl %edi
LCFI5:
leave
LCFI6:
ret
por supuesto, supongo que todos los expertos aquí saben qué rep movsb
significa rep movsb
.
Esta es la versión de asignación:
extern "C" void cpy2(float* d, float* s, size_t n)
{
while (n > 0) {
d[n] = s[n];
n--;
}
}
que produce el siguiente código:
_cpy2:
LFB394:
pushl %ebp
LCFI7:
movl %esp, %ebp
LCFI8:
pushl %ebx
LCFI9:
movl 8(%ebp), %ebx
movl 12(%ebp), %ecx
movl 16(%ebp), %eax
testl %eax, %eax
je L2
.p2align 2,,3
L5:
movl (%ecx,%eax,4), %edx
movl %edx, (%ebx,%eax,4)
decl %eax
jne L5
L2:
popl %ebx
LCFI10:
leave
LCFI11:
ret
Que mueve 4 bytes a la vez.
Use std::copy()
. Como el archivo de encabezado para notas de g++
:
Esta función en línea se reducirá a una llamada a @c memmove siempre que sea posible.
Probablemente, Visual Studio no sea muy diferente. Vaya con la manera normal y optimice una vez que tenga conocimiento del cuello de una botella. En el caso de una copia simple, el compilador probablemente ya esté optimizando para usted.