¿En qué casos debo usar memcpy sobre operadores estándar en C++?
¿Cuándo puedo obtener un mejor rendimiento usando memcpy
o cómo me beneficio de usarlo?
Por ejemplo:
float a[3]; float b[3];
Es el código:
memcpy(a, b, 3*sizeof(float));
más rápido de esta?
a[0] = b[0];
a[1] = b[1];
a[2] = b[2];
7 answers
La eficiencia no debe ser su preocupación.
Escribir código limpio y mantenible.
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 en 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();
}
Luego 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 resultó en: (comentarios añadidos 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 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
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-08-17 18:02:14
Puede usar memcpy
solo si los objetos que está copiando no tienen constructores explícitos, así como sus miembros (los llamados POD, "Datos antiguos simples"). Así que está bien llamar memcpy
para float
, pero está mal para, por ejemplo, std::string
.
Pero parte del trabajo ya se ha hecho para usted: std::copy
de <algorithm>
está especializado para tipos integrados (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.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-09-02 06:35:28
Los compiladores optimizan específicamente las llamadas memcpy
, al menos clang & gcc lo hace. Así que deberías preferirlo donde puedas.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-12-28 09:00:33
Use std::copy()
. Como el archivo de cabecera para g++
notas:
Esta función en línea se reducirá a una llamada a @c memmove siempre que sea posible.
Probablemente, Visual Studio no es muy diferente. Ir con la forma normal, y optimizar una vez que eres consciente de un cuello de botella. En el caso de una copia simple, el compilador probablemente ya esté optimizando para usted.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-12-28 09:11:11
No optes por micro-optimizaciones prematuras como usar memcpy de esta manera. El uso de la asignación es más claro y menos propenso a errores y cualquier compilador decente generará código adecuadamente eficiente. Si, y solo si, ha perfilado el código y encuentra que las asignaciones son un cuello de botella significativo, puede considerar algún tipo de microoptimización, pero en general siempre debe escribir código claro y robusto en primera instancia.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-12-28 09:07:21
Los beneficios de memcpy? Probablemente legibilidad. De lo contrario, tendría que hacer un número de asignaciones o tener un bucle for para copiar, ninguno de los cuales es tan simple y claro como solo hacer memcpy (por supuesto, siempre y cuando sus 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 una simple asignación, e incluso puede ser más rápido.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-12-28 09:04:58
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 que sea el cuello de botella coincide con la realidad.
Editar
Lo mismo se aplica a la matriz dinámica. Ya que mencionas C++ deberías usar std::copy()
algoritmo en eso caso.
Editar
Esta es la salida de código para Windows XP con GCC 4.5.0, compilado con la bandera-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 conjunto 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, asumo que todos los expertos aquí saben lo que rep movsb
significa.
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.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-12-28 10:53:48