2010-12-28 8 views
24

¿Cuándo puedo obtener un mejor rendimiento usando memcpy o cómo me beneficio al usarlo? Por ejemplo:¿En qué casos debo usar memcpy sobre los operadores estándar en C++?

float a[3]; float b[3]; 

es código:

memcpy(a, b, 3*sizeof(float)); 

más rápido que éste?

a[0] = b[0]; 
a[1] = b[1]; 
a[2] = b[2]; 
+2

Supongo que incluso el operador de asignación para float se implementaría utilizando memcpy. Por lo tanto, usar memcpy directamente para toda la matriz sería más rápido – Akhil

+4

No creo en su edición. ¿Por qué el segundo enfoque sería más rápido? memcpy() está diseñado específicamente para copiar áreas de memoria de un lugar a otro, por lo que debe ser tan eficiente como lo permita la arquitectura subyacente. Apuesto a que usará el ensamblaje apropiado cuando corresponda para hacer una copia de la memoria de bloques. –

Respuesta

45

La eficiencia no debe ser su preocupación.
Escriba el código de limpieza limpia.

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(); 
} 

Luego de 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 lugar a: (comentarios añadidos a mano)

======= // Copy by hand 
10a11,18 
> movq [email protected](%rip), %rcx 
> movq [email protected](%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 [email protected](%rip), %rcx 
> movq [email protected](%rip), %rdx 
> movq (%rdx), %rax 
> movq %rax, (%rcx) 
> movl 8(%rdx), %eax 
> movl %eax, 8(%rcx) 

======= // std::copy() 
10a11,14 
> movq [email protected](%rip), %rsi 
> movl $12, %edx 
> movq [email protected](%rip), %rdi 
> call _memmove 

Se agregaron los resultados del 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 
+20

+1. Y, dado que no escribió la conclusión obvia de esto, la llamada memcpy parece que está generando el código más eficiente. –

+1

Duda que hace una diferencia, pero '3 * sizeof (float)' debe ser 'sizeof a', de modo que si el tamaño de' a' cambia, la llamada a 'memcpy' se ajusta con él. –

+0

Huh. ¿Por qué la llamada a '_memmove' no está en línea? –

10

compiladores optimizar específicamente memcpy llamadas, al menos clang & gcc hace. Entonces deberías preferirlo donde sea que puedas.

+0

@ismail: los compiladores pueden optimizar 'memcpy', pero aún es menos probable que sea más rápido que el segundo enfoque. Por favor, lea la publicación de Simone. – Nawaz

+1

@Nawaz: No estoy de acuerdo. Es probable que memcpy() sea más rápido dado el soporte de arquitectura. De todos modos, esto es redundante ya que std :: copy (como lo describe @crazylammer) es probablemente la mejor solución. –

0

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, 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 especifica matrices dinámicas también.

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, supongo que todos los expertos aquí sabe lo rep movsb medios.

Esta es la versión asignación:

extern "C" void cpy2(float* d, float* s, size_t n) 
{ 
    while (n > 0) { 
     d[n] = s[n]; 
     n--; 
    } 
} 

que produce el código siguiente:

_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 se mueve 4 bytes a la vez.

+0

@Simone: el primer párrafo tiene sentido para mí. Ahora necesito verificarlo, porque no estoy seguro. :-) – Nawaz

+7

No creo que memcopy copie byte por byte. Está diseñado específicamente para copiar grandes fragmentos de memoria de manera muy eficiente. –

+0

Fuente por favor? Lo único que POSIX manda es [esto] (http: //pubs.opengroup.org/onlinepubs/9699919799/functions/memcpy.html). Por cierto, mira si [esta implementación] (http://www.gnu.org/software/mifluz/doc/doxydoc/memcpy2_8c-source.html) es tan rápida. – Simone

4

Las ventajas 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 simple asignación, y puede incluso ser más rápido.

14

Puede usar memcpy solo si los objetos que está copiando no tienen constructores explícitos, por lo que sus miembros (denominado POD, "Datos antiguos simples").Por lo tanto, está bien llamar al memcpy para float, pero es incorrecto para, por ejemplo, std::string.

Pero parte del trabajo ya se ha realizado: std::copy de <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.

+7

'std :: copy' se encuentra correctamente en' '; '' es estrictamente compatible con versiones anteriores. –

4

No utilice micropiezaciones 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á 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.

+1

¿Cómo se asignan N (donde N> 2) diferentes elementos de matriz uno por uno más claros que un solo 'memcpy'? 'memcpy (a, b, sizeof a)' es más claro porque, si el tamaño de 'a' y' b' cambia, no necesita agregar/eliminar asignaciones. –

+0

@Chris Lutz: debes pensar en la solidez del código a lo largo de su vida útil, p. ¿Qué sucede si en algún momento alguien cambia la declaración de a para que se convierta en un puntero en lugar de una matriz? La asignación no se rompería en este caso, pero la memcpy sí lo haría. –

+1

'memcpy' no se rompería (el truco' sizeof' se rompería, pero solo algunas personas lo usan). Tampoco 'std :: copy', que es demostrablemente superior a ambos en casi todos los aspectos. –

6

Use std::copy(). Como el archivo de encabezado 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. 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.

Cuestiones relacionadas