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];
È stato utile?

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.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top