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:

array[0] = 0;
array[1] = 0;
array[2] = 0;
array[3] = 0;
array[4] = 0;

  • 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

Benutzeravatar von Michael Burr
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).

Hier sind zum Beispiel einige Versionshinweise von GCC 4.3:

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). 🙂

    – abschalten

    16. Juli 2009 um 8:59 Uhr

  • Entspannen Sie sich, wenn Sie denken, dass “der Compiler besser ist als Sie” etwas ist, dem Sie folgen sollten, sehen Sie sich das an – liranuna.com/sse-intrinsics-optimizations-in-popular-compilers

    – LiraNuna

    30. Juli 2009 um 21:55 Uhr

  • @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

Roddys Benutzeravatar
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

1412190cookie-checkLöschen eines kleinen Integer-Arrays: Memset vs. For-Schleife

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy