Löschen eines kleinen Integer-Arrays: Memset vs. For-Schleife
Lesezeit: 6 Minuten
Es gibt zwei Möglichkeiten, ein Integer/Float-Array auf Null zu setzen:
memset(array, 0, sizeof(int)*arraysize);
oder:
for (int i=0; i <arraysize; ++i)
array[i]=0;
Offensichtlich ist memset für große schneller arraysize. Aber ab wann ist der Overhead von memset tatsächlich größer als der Overhead der for-Schleife? Zum Beispiel für ein Array der Größe 5 – was wäre am besten? Die erste, die 2. oder vielleicht sogar die ausgerollte Version:
Nun, ich kann den interessanten Teil nicht wirklich sehen. Die einzige wirkliche Antwort wird ein Benchmarking der drei Versionen auf einer bestimmten Plattform/einem bestimmten Compiler sein. Außerdem ist es nicht wirklich nützlich, die Antwort zu kennen, oder? Aber um den Benchmak-Code effizient zu schreiben;)
– Neuro
15. Juli 2009 um 21:17 Uhr
Ja, ich habe vor, das selbst zu machen. aber jetzt wird es das ganze Internet wissen! (zumindest für Windows mit gcc von mingw …)
– Claudius
15. Juli 2009 um 21:23 Uhr
Ich frage mich nur, wie Sie auf diese Frage gekommen sind?
– Artem Barger
15. Juli 2009 um 21:30 Uhr
Sie haben die dritte Version nicht erwähnt: int array[5] = {0};
– Hosam Ali
15. Juli 2009 um 22:13 Uhr
Das ist nur für die Initialisierung auf der Deklarationsseite. Die Formulare in der Frage können jederzeit verwendet werden, um ein vorhandenes Array zu löschen.
– niemand
16. Juli 2009 um 1:29 Uhr
Michael Burr
Aller Wahrscheinlichkeit nach wird memset() von Ihrem Compiler eingebettet (die meisten Compiler behandeln es als „intrinsisch“, was im Grunde bedeutet, dass es eingebettet ist, außer vielleicht bei den niedrigsten Optimierungen oder wenn es nicht ausdrücklich deaktiviert ist).
Codegenerierung der Blockbewegung (memcpy) und Blocksatz (memset) wurde umgeschrieben. GCC kann jetzt den besten Algorithmus (Schleife, ausgerollte Schleife, Anweisung mit Rep-Präfix oder ein Bibliotheksaufruf) auswählen, basierend auf der Größe des zu kopierenden Blocks und der CPU, für die optimiert wird. Eine neue Möglichkeit -minline-stringops-dynamically wurde hinzugefügt. Mit dieser Option werden String-Operationen unbekannter Größe so erweitert, dass kleine Blöcke per Inline-Code kopiert werden, während für große Blöcke ein Bibliotheksaufruf verwendet wird. Dies führt zu schnellerem Code als -minline-all-stringops wenn die Bibliotheksimplementierung Cache-Hierarchie-Hinweise verwenden kann. Die heuristische Auswahl des jeweiligen Algorithmus kann über überschrieben werden -mstringop-strategy. Neu auch memset von Werten ungleich 0 ist inline.
Es könnte für den Compiler möglich sein, etwas Ähnliches mit den von Ihnen angegebenen alternativen Beispielen zu tun, aber ich würde wetten, dass dies weniger wahrscheinlich ist.
Und sein grep-fähig und auf einen Blick sofort ersichtlich, was die Absicht ist, zu booten (nicht, dass die Schleife auch besonders schwer zu groken wäre).
Dies ist ein großartiges Beispiel für das oft gehörte „Der Compiler kann besser optimieren als Sie“. Nur wenige Anwendungsprogrammierer würden so viel Aufmerksamkeit auf einen einzelnen Anruf verwenden (und wenn sie es täten, würde ihre tatsächliche Anwendung wahrscheinlich darunter leiden). 🙂
@LiraLuna – das ist ein ziemlich interessanter Artikel, aber ich denke, Sie stimmen zu, dass memset()/memcpy() in einer anderen Klasse als SSE-Intrinsics sind, wenn es darum geht, wie viel Arbeit in die Compiler-Codegenerierung geflossen ist. Außerdem möchten Sie nur die Analyseebene durchführen, die Sie für Code durchgeführt haben, der wirklich leistungskritisch ist (oder vielleicht als akademische Übung) und daher Ihrer eingehenden Aufmerksamkeit würdig ist – nicht für jede einzelne Pufferkopie oder jeden Clear .
– Michael Burr
30. Juli 2009 um 22:44 Uhr
@LiraNuna: Update 6 Jahre später: Clang hat derzeit eine leistungsfähigere SIMD-Intrinsic-Optimierung als gcc. insbesondere für Shuffles kümmert es Clang nicht einmal, welche Intrinsics Sie überhaupt verwendet haben; es trifft lediglich seine eigene Wahl, welche Anweisungen ausgegeben werden, um das gleiche Mischergebnis zu erzielen. Dies kann tatsächlich nachteilig sein, wenn Sie versuchen, eine Sequenz von Hand zu stimmen, und die aktuellen Auswahlmöglichkeiten von Clang sind unvollkommen, aber in den meisten Fällen ist es großartig. (Und in Zukunft, wenn der Optimierer von clang/LLVM die optimalen Mischvorgänge für mehr Fälle kennt, wird es kein Problem sein.)
– Peter Cordes
10. Juli 2016 um 17:14 Uhr
Wie Michael bereits angemerkt hat, optimieren gcc und ich denke, die meisten anderen Compiler dies bereits sehr gut. Zum Beispiel schaltet gcc dies um
char arr[5];
memset(arr, 0, sizeof arr);
hinein
movl $0x0, <arr+0x0>
movb $0x0, <arr+0x4>
Besser geht es nicht…
Für etwas größere Arrays können 64-Bit-Register oder SIMD verwendet werden, um jeweils 8/16/32… Bytes auf Null zu setzen
– phuklv
25. Februar 2017 um 9:00 Uhr
Roddy
Ohne Messung ist die Frage nicht zu beantworten. Es hängt vollständig von den Implementierungen des Compilers, der CPU und der Laufzeitbibliothek ab.
memset() kann etwas wie ein “Code-Geruch” sein, da es anfällig für Pufferüberläufe und Parameterumkehrungen sein kann und die unglückliche Fähigkeit hat, nur “Byte-weise” zu löschen. Es ist jedoch eine sichere Wette, dass es in allen außer extremen Fällen “am schnellsten” sein wird.
Ich neige dazu, ein Makro zu verwenden, um dies zu umschließen, um einige der Probleme zu vermeiden:
#define CLEAR(s) memset(&(s), 0, sizeof(s))
Dies umgeht die Größenberechnungen und beseitigt das Problem des Vertauschens der Längen- und Wertparameter.
Kurz gesagt, verwenden Sie memset() “unter der Haube”. Schreiben Sie, was Sie beabsichtigen, und lassen Sie den Compiler sich um Optimierungen kümmern. Die meisten sind unglaublich gut darin.
++ Oh mein Gott! Du verwendest ein Makro!? Besser untertauchen!
– Mike Dunlavey
16. Juli 2009 um 0:44 Uhr
Ich denke, Sie haben den Makroparameter (x) erstellt, aber (s) im eigentlichen Körper verwendet … möchten Sie das vielleicht bearbeiten.
– mimu
16. Juli 2009 um 1:19 Uhr
@micmoo – danke – behoben. @mike zu Makros: ja … aber sie sind in C unvermeidlich. Die C++-Antwort auf diese Frage wäre sehr anders!
– Roddy
16. Juli 2009 um 8:41 Uhr
Ihr Makro würde es schwierig machen, das Problem mit Code wie diesem zu erkennen: char* foo = malloc(80); CLEAR(foo);
– Dan Breslau
30. Juli 2009 um 21:44 Uhr
@Roddy, in dieser Diskussion ist sicherlich etwas Raum für Subjektivität. Meiner Erfahrung nach fügt ein Makroaufruf wie dieser mehr Verschleierung hinzu, als er wert ist. Ein erfahrener Programmierer würde beim Lesen von wahrscheinlich sofort zweimal hinschauen memset Anrufe, die Sie gerade geschrieben haben. Aber sehe den Fehler drin char* foo = malloc(80); CLEAR(foo); erfordert die Kenntnis der Definition von CLEAR. (Ein erfahrener Programmierer würde wohl sofort wissen, dass es keine vernünftige Definition von CLEAR könnte für einen dynamisch zugewiesenen Zeiger korrekt sein; aber das ist nicht die Art von Erfahrung, auf die ich mich verlassen möchte.)
– Dan Breslau
5. Juli 2012 um 15:25 Uhr
In Anbetracht dieses Codes an sich ist schon alles gesagt. Aber wenn Sie es in seinem Programm berücksichtigen, von dem ich nichts weiß, kann etwas anderes getan werden. Wenn dieser Code beispielsweise jedes Mal ausgeführt werden soll, um ein Array zu löschen, könnten Sie einen Thread ausführen, der ständig ein neues Array von Nullelementen zuweist, die einer globalen Variablen zugewiesen sind, die Ihr Code, wenn das Array gelöscht werden muss, weist einfach darauf hin.
Dies ist eine dritte Möglichkeit. Natürlich, wenn Sie vorhaben, Ihren Code auf einem Prozessor mit mindestens zwei Kernen auszuführen, und dies sinnvoll ist. Außerdem muss der Code mehr als einmal ausgeführt werden, um die Vorteile zu sehen. Für nur eine einmalige Ausführung könnten Sie ein mit Nullen gefülltes Array deklarieren und dann bei Bedarf darauf zeigen.
Hoffe, das kann jemandem helfen
14121900cookie-checkLöschen eines kleinen Integer-Arrays: Memset vs. For-Schleifeyes
Nun, ich kann den interessanten Teil nicht wirklich sehen. Die einzige wirkliche Antwort wird ein Benchmarking der drei Versionen auf einer bestimmten Plattform/einem bestimmten Compiler sein. Außerdem ist es nicht wirklich nützlich, die Antwort zu kennen, oder? Aber um den Benchmak-Code effizient zu schreiben;)
– Neuro
15. Juli 2009 um 21:17 Uhr
Ja, ich habe vor, das selbst zu machen. aber jetzt wird es das ganze Internet wissen! (zumindest für Windows mit gcc von mingw …)
– Claudius
15. Juli 2009 um 21:23 Uhr
Ich frage mich nur, wie Sie auf diese Frage gekommen sind?
– Artem Barger
15. Juli 2009 um 21:30 Uhr
Sie haben die dritte Version nicht erwähnt:
int array[5] = {0};
– Hosam Ali
15. Juli 2009 um 22:13 Uhr
Das ist nur für die Initialisierung auf der Deklarationsseite. Die Formulare in der Frage können jederzeit verwendet werden, um ein vorhandenes Array zu löschen.
– niemand
16. Juli 2009 um 1:29 Uhr