In quali casi occorre usare memcpy sopra operatori standard in C ++?
-
13-10-2019 - |
Domanda
Quando posso ottenere prestazioni migliori utilizzando memcpy
o come faccio a trarre vantaggio dall'utilizzo di esso?
Ad esempio:
float a[3]; float b[3];
è il codice:
memcpy(a, b, 3*sizeof(float));
più veloce di questa?
a[0] = b[0];
a[1] = b[1];
a[2] = b[2];
Soluzione
L'efficienza non dovrebbe essere la vostra preoccupazione.
Scrivere il codice mantenibile pulita.
Mi da fastidio che così tante risposte indicano che il memcpy () è inefficiente. È stato progettato per essere il modo più efficace di blocchi di copia della memoria (per i programmi C).
Così ho scritto il seguente come un test:
#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();
}
Poi per confrontare il codice 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
Il risultato è: (commenti aggiunti 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
risultati aggiunti Timing per l'esecuzione del sopra all'interno di un ciclo di 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
Altri suggerimenti
È possibile utilizzare memcpy
solo se la si sta copiando gli oggetti non hanno costruttori espliciti, così come i loro membri (i cosiddetti POD, "Plain Old Data"). Quindi è OK per chiamata memcpy
per float
, ma è sbagliato per, ad esempio, std::string
.
Ma parte del lavoro è già stato fatto per voi: std::copy
da <algorithm>
è specializzato per tipi built-in (e, eventualmente, per ogni altro POD-tipo - dipende dalla implementazione STL). Così scriveva std::copy(a, a + 3, b)
è veloce (dopo l'ottimizzazione del compilatore) come memcpy
, ma è meno soggetto a errori.
chiamate memcpy
compilatori specificamente ottimizzare, almeno clang & gcc fa. Così si dovrebbe preferire dove possibile.
Usa std::copy()
. Come il file di intestazione per le note g++
:
Questa funzione inline si riducono a una chiamata a memmove @c quando possibile.
Probabilmente, Visual Studio non è molto diversa. Vai con il modo normale, e ottimizzare una volta che sei a conoscenza di un collo di bottiglia. Nel caso di una semplice copia, il compilatore è probabilmente già ottimizzazione per voi.
Non andare per prematuri micro-ottimizzazioni come l'utilizzo di memcpy come questo. Utilizzando assegnazione è più chiara e meno soggetto a errori e qualsiasi compilatore discreto genererà codice opportunamente efficiente. Se, e solo se, si è profilato il codice ed ha trovato le assegnazioni di essere un collo di bottiglia significativo allora si può considerare una sorta di micro-ottimizzazione, ma in generale si dovrebbe sempre scrivere, codice robusto chiaro in prima istanza.
I vantaggi di memcpy? Probabilmente la leggibilità. In caso contrario, si dovrebbe o fare una serie di incarichi o di avere un ciclo for per la copia, nessuno dei quali sono il più semplice e chiaro come solo facendo memcpy (ovviamente, a patto che i tipi di sono semplici e non richiedono la costruzione / distruzione).
Inoltre, memcpy è generalmente relativamente ottimizzato per piattaforme specifiche, al punto che non sarà più di tanto più lento di assegnazione semplice, e può anche essere più veloce.
Si suppone, come ha detto Nawaz, la versione assegnazione dovrebbe essere più veloce sulla maggior parte della piattaforma. Questo perché memcpy()
copierà byte per byte, mentre la seconda versione potrebbe copiare 4 byte alla volta.
Come è sempre il caso, si dovrebbe sempre il profilo delle applicazioni per essere sicuri che ciò che si aspetta di essere il collo di bottiglia corrisponde alla realtà.
Modifica
Lo stesso vale per array dinamico. Dal momento che si parla di C ++ si dovrebbe usare un algoritmo std::copy()
in quel caso.
Modifica
Questa è l'uscita di codice per Windows XP con GCC 4.5.0, compilato con -O3 bandiera:
extern "C" void cpy(float* d, float* s, size_t n)
{
memcpy(d, s, sizeof(float)*n);
}
ho fatto questa funzione perché OP specificato array dinamici troppo.
Output di montaggio è il seguente:
_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
Naturalmente, presumo tutti gli esperti qui sa cosa significa rep movsb
.
Questa è la versione di assegnazione:
extern "C" void cpy2(float* d, float* s, size_t n)
{
while (n > 0) {
d[n] = s[n];
n--;
}
}
da cui si ricava il seguente codice:
_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
che si muove 4 byte alla volta.