Ist es besser, memcpy
wie unten gezeigt zu verwenden , oder ist es besser, std::copy()
in Bezug auf die Leistung zu verwenden? Warum?
char *bits = NULL;
...
bits = new (std::nothrow) char[((int *) copyMe->bits)[0]];
if (bits == NULL)
{
cout << "ERROR Not enough memory.\n";
exit(1);
}
memcpy (bits, copyMe->bits, ((int *) copyMe->bits)[0]);
c++
performance
optimization
user576670
quelle
quelle
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 *)
.)std::vector<char>
? Oder weil Sie sagenbits
,std::bitset
?(int*) copyMe->bits[0]
bedeutet?int
Diktat seiner Größe enthält, aber das scheint ein Rezept für eine implementierungsdefinierte Katastrophe zu sein, wie so viele andere Dinge hier.(int *)
Besetzung nur ein reines undefiniertes Verhalten, nicht implementierungsdefiniert. Der Versuch, Typ-Punning über eine Besetzung durchzuführen, verstößt gegen strenge Aliasing-Regeln und ist daher im Standard völlig undefiniert. (Auch in C ++, obwohl nicht C, können Sie kein Wortspiel über aunion
eingeben.) Die einzige Ausnahme ist, wenn Sie in eine Variante von konvertierenchar*
, die Zulage jedoch nicht symmetrisch ist.Antworten:
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 jedochstd::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 derstd::copy
Version gewechselt . Mein Code nutzt die Möglichkeit, Daten in möglichst großen Blöcken abzurufen (viele andere Implementierungen arbeiten mitchar
/char *
, während ich mitT
/ arbeiteT *
(wobeiT
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
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
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, diestd::copy
/ aufgerufen hatmemcpy
. 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
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 diesmemcpy
legalstd::copy
ist, keine schlechtere Leistung erzielt werden sollte. Die triviale Implementierungstd::copy
dass aufschiebt zumemcpy
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 aufrufenstd::copy
, behält die Funktion die Typen bei.memcpy
arbeitet weitervoid *
, wodurch fast alle nützlichen Informationen verworfen werden. Wenn ich beispielsweise ein Array von übergebestd::uint64_t
, kann der Compiler oder Bibliotheksimplementierer möglicherweise die 64-Bit-Ausrichtung mit nutzenstd::copy
, dies ist jedoch möglicherweise schwierigermemcpy
. 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 esmemcpy
mit keinem modernen Optimierungs-Compiler langsamer und manchmal schneller wird. Darüber hinaus können Sie alles, wasmemcpy
Sie könnenstd::copy
.memcpy
erlaubt keine Überlappung in den Puffern, wohingegenstd::copy
Stützen in einer Richtung überlappen (mitstd::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.quelle
std::copy
2,99% oder 0,72% oder -0,11% schneller sind alsmemcpy
diese Zeiten, in denen das gesamte Programm ausgeführt wird. Im Allgemeinen bin ich jedoch der Meinung, dass Benchmarks in echtem Code nützlicher sind als Benchmarks in gefälschtem Code. Mein gesamtes Programm hat diese Änderung in der Ausführungsgeschwindigkeit bekommen. Die tatsächlichen Auswirkungen nur der beiden Kopierschemata weisen größere Unterschiede auf als hier gezeigt, wenn sie isoliert betrachtet werden. Dies zeigt jedoch, dass sie messbare Unterschiede im tatsächlichen Code aufweisen können.memcpy
undstd::copy
hat unterschiedliche Implementierungen, so dass der Compiler in einigen Fällen den umgebenden Code und den tatsächlichen Speicherkopiecode als einen integralen Code optimiert. Mit anderen Worten, manchmal ist eines besser als das andere, und mit anderen Worten, die Entscheidung, welche verwendet werden soll, ist verfrüht oder sogar dumm, weil in jeder Situation neue Forschung betrieben werden muss und darüber hinaus normalerweise Programme entwickelt werden, also danach Einige geringfügige Änderungen des Funktionsvorteils gegenüber anderen können verloren gehen.std::copy
um eine triviale Inline-Funktion handelt, die nur aufruft,memcpy
wenn sie legal ist. Grundlegendes Inlining würde negative Leistungsunterschiede beseitigen. Ich werde den Beitrag mit einer Erklärung aktualisieren, warum std :: copy möglicherweise schneller ist.Alle Compiler, die ich kenne, ersetzen eine einfache
std::copy
durch eine,memcpy
wenn es angebracht ist, oder noch besser, die Kopie so zu vektorisieren, dass sie noch schneller als eine istmemcpy
.Auf jeden Fall: Profilieren und selbst herausfinden. Verschiedene Compiler werden unterschiedliche Dinge tun, und es ist durchaus möglich, dass sie nicht genau das tun, was Sie verlangen.
Siehe diese Präsentation zu Compiler-Optimierungen (pdf).
Hier ist, was GCC für einen einfachen
std::copy
POD-Typ tut .Hier ist die Demontage (nur mit
-O
Optimierung), die den Aufruf an Folgendes zeigtmemmove
:Wenn Sie die Funktionssignatur in ändern
dann wird das
memmove
zumemcpy
einer leichten Leistungsverbesserung. Beachten Sie, dassmemcpy
selbst stark vektorisiert wird.quelle
memmove
Sollte aber nicht schneller sein, sondern etwas langsamer, da die Möglichkeit einer Überlappung der beiden Datenbereiche berücksichtigt werden muss. Ich denke,std::copy
erlaubt überlappende Daten, und so muss es aufrufenmemmove
.memcpy
. Es lässt mich glauben, dass GCC prüft, ob es eine Speicherüberlappung gibt.std::copy
erlaubt Überlappung in eine Richtung, aber nicht in die andere. Der Anfang der Ausgabe kann nicht innerhalb des Eingabebereichs liegen, aber der Anfang der Eingabe darf innerhalb des Ausgabebereichs liegen. Dies ist etwas seltsam, da die Reihenfolge der Zuweisungen definiert ist und ein Aufruf möglicherweise UB ist, obwohl die Auswirkung dieser Zuweisungen in dieser Reihenfolge definiert ist. Aber ich nehme an, die Einschränkung erlaubt Vektorisierungsoptimierungen.Verwenden Sie immer
std::copy
damemcpy
ist begrenzt auf nur C-Stil POD Strukturen, und der Compiler wird wahrscheinlich Anrufe ersetzen , umstd::copy
mit ,memcpy
wenn die Ziele sind in der Tat POD.Außerdem
std::copy
kann es mit vielen Iteratortypen verwendet werden, nicht nur mit Zeigern.std::copy
ist flexibler ohne Leistungsverlust und ist der klare Gewinner.quelle
std::copy(container.begin(), container.end(), destination);
den Inhalt voncontainer
(alles zwischenbegin
undend
) in den durch angegebenen Pufferdestination
.std::copy
erfordert keine Shenanigans wie&*container.begin()
oder&container.back() + 1
.Theoretisch
memcpy
könnte dies einen geringfügigen , nicht wahrnehmbaren , infinitesimalen Leistungsvorteil haben, nur weil es nicht die gleichen Anforderungen wie hatstd::copy
. Aus der Manpage vonmemcpy
:Mit anderen Worten,
memcpy
kann die Möglichkeit überlappender Daten ignorieren. (Das Übergeben überlappender Arrays anmemcpy
ist ein undefiniertes Verhalten.) Dahermemcpy
muss nicht explizit nach dieser Bedingung gesuchtstd::copy
werden , während sie verwendet werden kann, solange sich derOutputIterator
Parameter nicht im Quellbereich befindet. Beachten Sie, dass dies nicht gleichbedeutend ist mit der Aussage, dass sich Quell- und Zielbereich nicht überschneiden dürfen.Also da
std::copy
hat etwas andere Anforderungen, in der Theorie sollte es sein , leicht (mit einem extremen Wert auf leicht ) langsamer, da es wahrscheinlich für überlappende C-Arrays überprüfen wird, oder delegiert das Kopieren von C-Arraysmemmove
, die das ausführen muss prüfen. In der Praxis werden Sie (und die meisten Profiler) wahrscheinlich nicht einmal einen Unterschied feststellen.Wenn Sie nicht mit PODs arbeiten , können Sie diese natürlich
memcpy
sowieso nicht verwenden .quelle
std::copy<char>
. Aberstd::copy<int>
kann davon ausgehen , dass seine Eingänge sind int-ausgerichtet sind . Das wird einen weitaus größeren Unterschied machen, da es jedes Element betrifft. Überlappung ist eine einmalige Prüfung.memcpy
Ich habe gesehen, wie die Ausrichtung überprüft wurde, und versucht, Wörter anstatt byteweise zu kopieren.memcpy
Schnittstelle gehen die Ausrichtungsinformationen verloren. Dahermemcpy
müssen zur Laufzeit Ausrichtungsprüfungen durchgeführt werden, um nicht ausgerichtete Anfänge und Enden zu verarbeiten. Diese Schecks mögen billig sein, sind aber nicht kostenlos. Währendstd::copy
kann diese Überprüfungen vermeiden und vektorisieren. Der Compiler kann auch beweisen, dass sich Quell- und Zielarrays nicht überlappen und erneut vektorisieren, ohne dass der Benutzer zwischenmemcpy
und wählen mussmemmove
.Meine Regel ist einfach. Wenn Sie C ++ verwenden, bevorzugen Sie C ++ - Bibliotheken und nicht C :)
quelle
std::end(c_arr)
anstatt sie zu verwendenc_arr + i_hope_this_is_the_right_number_of elements
. und vielleicht noch wichtiger, klarer. Und das wäre der Punkt, den ich in diesem speziellen Fall hervorhole:std::copy()
Ist idiomatischer, wartbarer, wenn sich die Typen der Iteratoren später ändern, führt zu einer klareren Syntax usw.std::copy
ist sicherer, da die übergebenen Daten korrekt kopiert werden, falls es sich nicht um POD-Typen handelt.memcpy
kopiert gerne einstd::string
Objekt byteweise in eine neue Darstellung.Nur eine kleine Ergänzung: Der Geschwindigkeitsunterschied zwischen
memcpy()
undstd::copy()
kann sehr unterschiedlich sein, je nachdem, ob Optimierungen aktiviert oder deaktiviert sind. Mit g ++ 6.2.0 und ohne Optimierungenmemcpy()
gewinnt eindeutig:Wenn Optimierungen aktiviert sind (
-O3
), sieht alles wieder ziemlich gleich aus:Je größer das Array, desto weniger macht sich der Effekt bemerkbar, aber selbst bei
N=1000
memcpy()
ist er etwa doppelt so schnell, wenn keine Optimierungen aktiviert sind.Quellcode (erfordert Google Benchmark):
quelle
Wenn Sie wirklich maximale Kopierleistung benötigen (was Sie möglicherweise nicht tun), verwenden Sie keine von beiden .
Es kann viel getan werden, um das Kopieren des Speichers zu optimieren - noch mehr, wenn Sie bereit sind, mehrere Threads / Kerne dafür zu verwenden. Siehe zum Beispiel:
Was fehlt / ist nicht optimal in dieser memcpy-Implementierung?
Sowohl die Frage als auch einige der Antworten haben Implementierungen oder Links zu Implementierungen vorgeschlagen.
quelle
Die Profilerstellung zeigt diese Aussage:
std::copy()
ist immer so schnell wiememcpy()
oder schneller ist falsch.Mein System:
Der Code (Sprache: c ++):
Red Alert wies darauf hin, dass der Code memcpy von Array zu Array und std :: copy von Array zu Vektor verwendet. Das könnte ein Grund für eine schnellere Erinnerung sein.
Da gibt es
v.reserve (sizeof (arr1));
Es darf keinen Unterschied in der Kopie zum Vektor oder Array geben.
Der Code ist so festgelegt, dass in beiden Fällen ein Array verwendet wird. memcpy noch schneller:
quelle
std::copy
von einem Vektor zu einem Array dauert alsomemcpy
fast doppelt so lange? Diese Daten sind sehr verdächtig. Ich habe Ihren Code mit gcc mit -O3 kompiliert, und die generierte Assembly ist für beide Schleifen gleich. Jeder Zeitunterschied, den Sie auf Ihrer Maschine beobachten, ist also nur zufällig.