Sto studiando gli hotspot delle prestazioni in un'applicazione che trascorre il 50% del suo tempo in memmove (3). L'applicazione inserisce milioni di interi a 4 byte in array ordinati e utilizza memmove per spostare i dati "a destra" per fare spazio al valore inserito.
La mia aspettativa era che la copia della memoria fosse estremamente veloce e sono rimasto sorpreso dal fatto che così tanto tempo sia stato speso in memmove. Ma poi ho avuto l'idea che memmove sia lento perché sta spostando regioni sovrapposte, che devono essere implementate in un ciclo stretto, invece di copiare grandi pagine di memoria. Ho scritto un piccolo microbenchmark per scoprire se c'era una differenza di prestazioni tra memcpy e memmove, aspettandomi che memcpy vincesse a mani basse.
Ho eseguito il mio benchmark su due macchine (core i5, core i7) e ho visto che memmove è effettivamente più veloce di memcpy, sul vecchio core i7 addirittura quasi il doppio! Ora cerco spiegazioni.
Ecco il mio punto di riferimento. Copia 100 mb con memcpy, quindi si sposta di circa 100 mb con memmove; origine e destinazione si sovrappongono. Vengono provate varie "distanze" per origine e destinazione. Ogni test viene eseguito 10 volte, viene stampato il tempo medio.
https://gist.github.com/cruppstahl/78a57cdf937bca3d062c
Ecco i risultati sul Core i5 (Linux 3.5.0-54-generic # 81 ~ precise1-Ubuntu SMP x86_64 GNU / Linux, gcc è 4.6.3 (Ubuntu / Linaro 4.6.3-1ubuntu5). Il numero tra parentesi è la distanza (dimensione del gap) tra la sorgente e la destinazione:
memcpy 0.0140074
memmove (002) 0.0106168
memmove (004) 0.01065
memmove (008) 0.0107917
memmove (016) 0.0107319
memmove (032) 0.0106724
memmove (064) 0.0106821
memmove (128) 0.0110633
Memmove è implementato come un codice assembler ottimizzato per SSE, copiando da dietro a davanti. Utilizza il prefetch hardware per caricare i dati nella cache e copia 128 byte nei registri XMM, quindi li memorizza nella destinazione.
( memcpy-ssse3-back.S , righe 1650 ff)
L(gobble_ll_loop):
prefetchnta -0x1c0(%rsi)
prefetchnta -0x280(%rsi)
prefetchnta -0x1c0(%rdi)
prefetchnta -0x280(%rdi)
sub $0x80, %rdx
movdqu -0x10(%rsi), %xmm1
movdqu -0x20(%rsi), %xmm2
movdqu -0x30(%rsi), %xmm3
movdqu -0x40(%rsi), %xmm4
movdqu -0x50(%rsi), %xmm5
movdqu -0x60(%rsi), %xmm6
movdqu -0x70(%rsi), %xmm7
movdqu -0x80(%rsi), %xmm8
movdqa %xmm1, -0x10(%rdi)
movdqa %xmm2, -0x20(%rdi)
movdqa %xmm3, -0x30(%rdi)
movdqa %xmm4, -0x40(%rdi)
movdqa %xmm5, -0x50(%rdi)
movdqa %xmm6, -0x60(%rdi)
movdqa %xmm7, -0x70(%rdi)
movdqa %xmm8, -0x80(%rdi)
lea -0x80(%rsi), %rsi
lea -0x80(%rdi), %rdi
jae L(gobble_ll_loop)
Perché memmove è più veloce di memcpy? Mi aspetto che memcpy copi le pagine di memoria, il che dovrebbe essere molto più veloce del loop. Nel peggiore dei casi, mi aspetto che memcpy sia veloce quanto memmove.
PS: so che non posso sostituire memmove con memcpy nel mio codice. So che il codice di esempio mescola C e C ++. Questa domanda è davvero solo per scopi accademici.
AGGIORNAMENTO 1
Ho eseguito alcune varianti dei test, in base alle varie risposte.
- Quando si esegue memcpy due volte, la seconda esecuzione è più veloce della prima.
- Quando si "tocca" il buffer di destinazione di memcpy (
memset(b2, 0, BUFFERSIZE...)
), anche la prima esecuzione di memcpy è più veloce. - memcpy è ancora un po 'più lento di memmove.
Ecco i risultati:
memcpy 0.0118526
memcpy 0.0119105
memmove (002) 0.0108151
memmove (004) 0.0107122
memmove (008) 0.0107262
memmove (016) 0.0108555
memmove (032) 0.0107171
memmove (064) 0.0106437
memmove (128) 0.0106648
La mia conclusione: sulla base di un commento di @Oliver Charlesworth, il sistema operativo deve impegnare la memoria fisica non appena si accede al buffer di destinazione memcpy per la prima volta (se qualcuno sa come "provarlo", aggiungi una risposta! ). Inoltre, come ha detto @Mats Petersson, memmove è più adatto alla cache di memcpy.
Grazie per tutte le ottime risposte e commenti!