Ich werde hier gegen die allgemeine Weisheit verstoßen, std::copy
die einen leichten, fast unmerklichen Leistungsverlust zur Folge haben wird. Ich habe gerade einen Test durchgeführt und festgestellt, dass dies nicht wahr ist: Ich habe einen Leistungsunterschied festgestellt. Der Gewinner war jedoch std::copy
.
Ich habe eine C ++ SHA-2-Implementierung geschrieben. In meinem Test habe ich 5 Strings mit allen vier SHA-2-Versionen (224, 256, 384, 512) gehasht und 300-mal wiederholt. Ich messe Zeiten mit Boost.timer. Dieser 300-Schleifen-Zähler reicht aus, um meine Ergebnisse vollständig zu stabilisieren. Ich habe den Test jeweils 5 Mal ausgeführt und dabei zwischen der memcpy
Version und der std::copy
Version gewechselt . Mein Code nutzt die Möglichkeit, Daten in möglichst großen Blöcken abzurufen (viele andere Implementierungen arbeiten mit char
/ char *
, während ich mit T
/ arbeite T *
(wobei T
der größte Typ in der Implementierung des Benutzers das richtige Überlaufverhalten aufweist), sodass ein schneller Speicherzugriff auf die Die größten Typen, die ich kann, sind für die Leistung meines Algorithmus von zentraler Bedeutung. Dies sind meine Ergebnisse:
Zeit (in Sekunden), um den Lauf der SHA-2-Tests abzuschließen
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%
Durchschnittliche Geschwindigkeitssteigerung von std :: copy gegenüber memcpy: 2,99%
Mein Compiler ist gcc 4.6.3 unter Fedora 16 x86_64. Meine Optimierungsflags sind -Ofast -march=native -funsafe-loop-optimizations
.
Code für meine SHA-2-Implementierungen.
Ich habe beschlossen, auch meine MD5-Implementierung zu testen. Die Ergebnisse waren viel weniger stabil, also entschied ich mich für 10 Läufe. Nach meinen ersten Versuchen erhielt ich jedoch Ergebnisse, die von Lauf zu Lauf sehr unterschiedlich waren. Ich vermute also, dass eine Art Betriebssystemaktivität im Gange war. Ich beschloss, von vorne zu beginnen.
Gleiche Compilereinstellungen und Flags. Es gibt nur eine Version von MD5 und diese ist schneller als SHA-2. Daher habe ich 3000 Schleifen mit einem ähnlichen Satz von 5 Testzeichenfolgen durchgeführt.
Dies sind meine letzten 10 Ergebnisse:
Zeit (in Sekunden), um die Ausführung der MD5-Tests abzuschließen
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%
Durchschnittliche Gesamtabnahme der Geschwindigkeit von std :: copy über memcpy: 0,11%
Code für meine MD5-Implementierung
Diese Ergebnisse legen nahe, dass es eine Optimierung gibt, die std :: copy in meinen SHA-2-Tests std::copy
verwendet hat und die in meinen MD5-Tests nicht verwendet werden konnte. In den SHA-2-Tests wurden beide Arrays in derselben Funktion erstellt, die std::copy
/ aufgerufen hat memcpy
. In meinen MD5-Tests wurde eines der Arrays als Funktionsparameter an die Funktion übergeben.
Ich habe ein bisschen mehr getestet, um zu sehen, was ich tun kann, um std::copy
wieder schneller zu werden. Die Antwort stellte sich als einfach heraus: Aktivieren Sie die Optimierung der Verbindungszeit. Dies sind meine Ergebnisse bei aktiviertem LTO (Option -flto in gcc):
Zeit (in Sekunden), um die Ausführung der MD5-Tests mit -flto abzuschließen
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%
Durchschnittliche Geschwindigkeitssteigerung von std :: copy gegenüber memcpy: 0,72%
Zusammenfassend scheint es keine Leistungseinbußen für die Verwendung zu geben std::copy
. Tatsächlich scheint es einen Leistungsgewinn zu geben.
Erklärung der Ergebnisse
Warum also std::copy
einen Leistungsschub geben?
Erstens würde ich nicht erwarten, dass es für eine Implementierung langsamer wird, solange die Optimierung des Inlining aktiviert ist. Alle Compiler inline aggressiv; Dies ist möglicherweise die wichtigste Optimierung, da sie so viele andere Optimierungen ermöglicht. std::copy
kann (und ich vermute, dass alle Implementierungen in der realen Welt dies tun) erkennen, dass die Argumente trivial kopierbar sind und dass der Speicher nacheinander angeordnet ist. Dies bedeutet, dass im schlimmsten Fall, wenn dies memcpy
legal std::copy
ist, keine schlechtere Leistung erzielt werden sollte. Die triviale Implementierung std::copy
dass aufschiebt zu memcpy
sollten Sie Ihre Compiler Kriterien „immer inline dies , wenn für Geschwindigkeit oder Größe zu optimieren“ erfüllen.
Hält jedoch std::copy
auch mehr von seinen Informationen. Wenn Sie aufrufen std::copy
, behält die Funktion die Typen bei. memcpy
arbeitet weiter void *
, wodurch fast alle nützlichen Informationen verworfen werden. Wenn ich beispielsweise ein Array von übergebe std::uint64_t
, kann der Compiler oder Bibliotheksimplementierer möglicherweise die 64-Bit-Ausrichtung mit nutzen std::copy
, dies ist jedoch möglicherweise schwieriger memcpy
. Viele Implementierungen solcher Algorithmen funktionieren, indem zuerst der nicht ausgerichtete Teil am Anfang des Bereichs, dann der ausgerichtete Teil und dann der nicht ausgerichtete Teil am Ende bearbeitet werden. Wenn garantiert ist, dass alles ausgerichtet ist, wird der Code einfacher und schneller und für den Verzweigungsprädiktor in Ihrem Prozessor einfacher zu korrigieren.
Vorzeitige Optimierung?
std::copy
ist in einer interessanten Position. Ich erwarte, dass es memcpy
mit keinem modernen Optimierungs-Compiler langsamer und manchmal schneller wird. Darüber hinaus können Sie alles, was memcpy
Sie können std::copy
. memcpy
erlaubt keine Überlappung in den Puffern, wohingegen std::copy
Stützen in einer Richtung überlappen (mit std::copy_backward
für die andere Überlappungsrichtung). memcpy
funktioniert nur auf Zeiger, std::copy
arbeitet auf allen Iteratoren ( std::map
, std::vector
, std::deque
, oder meine eigenen benutzerdefinierten Typ). Mit anderen Worten, Sie sollten nur verwenden, std::copy
wenn Sie Datenblöcke kopieren müssen.
char
dies je nach Implementierung signiert oder nicht signiert sein kann. Wenn die Anzahl der Bytes> = 128 sein kann, verwenden Sie sieunsigned char
für Ihre Byte-Arrays. (Die(int *)
Besetzung wäre auch sicherer(unsigned int *)
.)