Ho intenzione di andare contro la saggezza generale qui che std::copy
avrà una leggera, quasi impercettibile perdita di prestazioni. Ho appena fatto un test e ho scoperto che era falso: ho notato una differenza di prestazioni. Tuttavia, il vincitore è stato std::copy
.
Ho scritto un'implementazione C ++ SHA-2. Nel mio test, ho eseguito l'hashing di 5 stringhe utilizzando tutte e quattro le versioni SHA-2 (224, 256, 384, 512) e ho eseguito il loop 300 volte. Misuro i tempi usando Boost.timer. Quel contatore di 300 cicli è sufficiente per stabilizzare completamente i miei risultati. Ho eseguito il test 5 volte ciascuno, alternando tra la memcpy
versione e la std::copy
versione. Il mio codice sfrutta l'acquisizione di dati nel maggior numero possibile di blocchi (molte altre implementazioni funzionano con char
/ char *
, mentre io opero con T
/ T *
(dove T
è il tipo più grande nell'implementazione dell'utente che ha un corretto comportamento di overflow), quindi un rapido accesso alla memoria sul i tipi più grandi che posso sono fondamentali per le prestazioni del mio algoritmo. Questi sono i miei risultati:
Tempo (in secondi) per completare l'esecuzione dei test SHA-2
std::copy memcpy % increase
6.11 6.29 2.86%
6.09 6.28 3.03%
6.10 6.29 3.02%
6.08 6.27 3.03%
6.08 6.27 3.03%
Aumento medio totale della velocità di std :: copia su memcpy: 2,99%
Il mio compilatore è gcc 4.6.3 su Fedora 16 x86_64. Le mie bandiere di ottimizzazione sono -Ofast -march=native -funsafe-loop-optimizations
.
Codice per le mie implementazioni SHA-2.
Ho deciso di eseguire un test anche sulla mia implementazione MD5. I risultati erano molto meno stabili, quindi ho deciso di fare 10 corse. Tuttavia, dopo i miei primi tentativi, ho ottenuto risultati che variavano selvaggiamente da una corsa all'altra, quindi suppongo ci fosse una sorta di attività del sistema operativo in corso. Ho deciso di ricominciare.
Stesse impostazioni e flag del compilatore. Esiste una sola versione di MD5, ed è più veloce di SHA-2, quindi ho eseguito 3000 loop su un set simile di 5 stringhe di test.
Questi sono i miei 10 risultati finali:
Tempo (in secondi) per completare l'esecuzione dei test MD5
std::copy memcpy % difference
5.52 5.56 +0.72%
5.56 5.55 -0.18%
5.57 5.53 -0.72%
5.57 5.52 -0.91%
5.56 5.57 +0.18%
5.56 5.57 +0.18%
5.56 5.53 -0.54%
5.53 5.57 +0.72%
5.59 5.57 -0.36%
5.57 5.56 -0.18%
Diminuzione media totale della velocità di std :: copia su memcpy: 0,11%
Codice per la mia implementazione MD5
Questi risultati suggeriscono che c'è qualche ottimizzazione che std :: copy ha usato nei miei test SHA-2 che std::copy
non è stato possibile utilizzare nei miei test MD5. Nei test SHA-2, entrambi gli array sono stati creati nella stessa funzione che ha chiamato std::copy
/ memcpy
. Nei miei test MD5, uno degli array è stato passato alla funzione come parametro di funzione.
Ho fatto un po 'più di test per vedere cosa avrei potuto fare per std::copy
accelerare di nuovo. La risposta si è rivelata semplice: attiva l'ottimizzazione del tempo di collegamento. Questi sono i miei risultati con LTO attivato (opzione -flto in gcc):
Tempo (in secondi) per completare l'esecuzione dei test MD5 con -flto
std::copy memcpy % difference
5.54 5.57 +0.54%
5.50 5.53 +0.54%
5.54 5.58 +0.72%
5.50 5.57 +1.26%
5.54 5.58 +0.72%
5.54 5.57 +0.54%
5.54 5.56 +0.36%
5.54 5.58 +0.72%
5.51 5.58 +1.25%
5.54 5.57 +0.54%
Aumento medio totale della velocità di std :: copia su memcpy: 0,72%
In sintesi, non sembra esserci una penalità di prestazione per l'utilizzo std::copy
. In effetti, sembra esserci un miglioramento delle prestazioni.
Spiegazione dei risultati
Quindi perché std::copy
dare un impulso alle prestazioni?
Innanzitutto, non mi aspetto che sia più lento per qualsiasi implementazione, a condizione che l'ottimizzazione dell'inline sia attiva. Tutti i compilatori si allineano in modo aggressivo; è probabilmente l'ottimizzazione più importante perché consente tante altre ottimizzazioni. std::copy
può (e sospetto che tutte le implementazioni del mondo reale lo facciano) rilevare che gli argomenti sono banalmente copiabili e che la memoria è disposta in sequenza. Ciò significa che nel peggiore dei casi, quando memcpy
è legale, non std::copy
dovrebbe andare peggio. L'implementazione banale di std::copy
quel difensore memcpy
dovrebbe soddisfare i criteri del compilatore di "sempre in linea con questo quando si ottimizza per velocità o dimensioni".
Tuttavia, std::copy
conserva anche maggiori informazioni. Quando si chiama std::copy
, la funzione mantiene intatti i tipi. memcpy
opera su void *
, che scarta quasi tutte le informazioni utili. Ad esempio, se passo in un array di std::uint64_t
, il compilatore o l'implementatore di librerie potrebbero essere in grado di trarre vantaggio dall'allineamento a 64 bit std::copy
, ma potrebbe essere più difficile farlo con memcpy
. Molte implementazioni di algoritmi come questo funzionano lavorando prima sulla parte non allineata all'inizio dell'intervallo, quindi sulla parte allineata, quindi sulla parte non allineata alla fine. Se è garantito l'allineamento, il codice diventa più semplice e veloce e più facile da correggere per il predittore di diramazione nel processore.
Ottimizzazione prematura?
std::copy
è in una posizione interessante. Mi aspetto che non sia mai più lento memcpy
e talvolta più veloce con qualsiasi moderno compilatore di ottimizzazione. Inoltre, tutto ciò che puoi memcpy
, puoi std::copy
. memcpy
non consente alcuna sovrapposizione nei buffer, mentre i std::copy
supporti si sovrappongono in una direzione (con std::copy_backward
per l'altra direzione di sovrapposizione). memcpy
funziona solo su puntatori, std::copy
funziona su qualsiasi iteratori ( std::map
, std::vector
, std::deque
, o il mio tipo personalizzato). In altre parole, dovresti usare solo std::copy
quando hai bisogno di copiare blocchi di dati in giro.
char
può essere firmato o non firmato, a seconda dell'implementazione. Se il numero di byte può essere> = 128, utilizzareunsigned char
per le matrici di byte. (Anche il(int *)
cast sarebbe più sicuro(unsigned int *)
.)