¿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];
Author: Patryk Czachurski, 2010-12-28

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
 48
Author: Martin York,
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.

 14
Author: crazylammer,
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.

 10
Author: ismail,
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.

 6
Author: Thanatos,
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.

 5
Author: Paul R,
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.

 4
Author: Jamie,
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.

 0
Author: Simone,
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