Angenommen, wir haben ein T myarray[100]
mit T = int, unsigned int, long long int oder unsigned long long int, was ist der schnellste Weg, um den gesamten Inhalt auf Null zurückzusetzen (nicht nur zur Initialisierung, sondern um den Inhalt in meinem Programm mehrmals zurückzusetzen). ? Vielleicht mit Memset?
Gleiche Frage für ein dynamisches Array wie T *myarray = new T[100]
.
new
ist C ++ ...memset
wenn ich nur darüber sprach, wenn C ++ irgendwie involviert ist ... :)for
Schleife nicht schlagen . Aber überraschenderweise können Sie viel schlimmeres tun, wenn Sie versuchen, klug zu sein.Antworten:
memset
(von<string.h>
) ist wahrscheinlich der schnellste Standardweg, da es sich normalerweise um eine Routine handelt, die direkt in der Montage geschrieben und von Hand optimiert wird.Übrigens wäre in C ++ der idiomatische Weg,
std::fill
(von<algorithm>
) zu verwenden:das kann automatisch in eine optimier
memset
; Ich bin mir ziemlich sicher, dass es so schnell wiememset
beiint
s funktioniert , während es bei kleineren Typen etwas schlechter abschneidet, wenn der Optimierer nicht intelligent genug ist. Trotzdem, wenn Sie Zweifel haben, Profil.quelle
memset
eine Ganzzahl auf 0 gesetzt wird. Es gab keine spezifische Aussage, dass All-Bits-Null eine Darstellung von ist0
. Eine technische Berichtigung fügte eine solche Garantie hinzu, die in der ISO C-Norm 2011 enthalten ist. Ich glaube , dass alle Bits von Null ist eine gültige Darstellung0
für alle Integer - Typen in allen existierenden C und C ++ Implementierungen, weshalb der Ausschuss der Lage war , diese Anforderung hinzuzufügen. (Es gibt keine ähnliche Garantie für Gleitkomma- oder Zeigertypen.)0
. (Mit Füllbits besteht die Möglichkeit, dass All-Bits-Null eine Trap-Darstellung sein könnte). In jedem Fall soll der TC fehlerhaften Text anerkennen und ersetzen. Ab 2004 sollten wir also so tun, als ob C99 diesen Text immer enthalten würde.int (*myarray)[N] = malloc(sizeof(*myarray));
.N
sind, aber in den allermeisten Fällen, wenn Sie sie verwendet habenmalloc
, wussten Sie es nur zur Laufzeit.Diese Frage ist zwar ziemlich alt, erfordert jedoch einige Benchmarks, da sie nicht den idiomatischsten oder den Weg erfordert, der in der geringsten Anzahl von Zeilen geschrieben werden kann, sondern den schnellsten Weg. Und es ist albern, diese Frage ohne tatsächliche Tests zu beantworten. Also habe ich vier Lösungen verglichen, memset vs. std :: fill vs. ZERO der Antwort von AnT mit einer Lösung, die ich mit AVX intrinsics erstellt habe.
Beachten Sie, dass diese Lösung nicht generisch ist, sondern nur mit Daten von 32 oder 64 Bit funktioniert. Bitte kommentieren Sie, wenn dieser Code etwas falsch macht.
Ich werde nicht behaupten, dass dies die schnellste Methode ist, da ich kein Experte für Optimierung auf niedriger Ebene bin. Es ist vielmehr ein Beispiel für eine korrekte architekturabhängige Implementierung, die schneller als memset ist.
Nun zu den Ergebnissen. Ich habe die Leistung für Arrays der Größe 100 int und long long berechnet, sowohl statisch als auch dynamisch zugewiesen. Mit Ausnahme von msvc, das bei statischen Arrays eine Eliminierung des toten Codes durchgeführt hat, waren die Ergebnisse äußerst vergleichbar, sodass nur die Leistung dynamischer Arrays angezeigt wird. Zeitmarkierungen sind ms für 1 Million Iterationen unter Verwendung der niedrigpräzisen Uhrfunktion von time.h.
clang 3.8 (Mit dem clang-cl-Frontend Optimierungsflags = / OX / arch: AVX / Oi / Ot)
gcc 5.1.0 (Optimierungsflags: -O3 -march = native -mtune = native -mavx):
msvc 2015 (Optimierungsflags: / OX / arch: AVX / Oi / Ot):
Hier ist viel Interessantes los: llvm töten gcc, MSVCs typische fleckige Optimierungen (es führt eine beeindruckende Eliminierung von totem Code auf statischen Arrays durch und hat dann eine schreckliche Leistung beim Füllen). Obwohl meine Implementierung erheblich schneller ist, kann dies nur daran liegen, dass das Löschen von Bits viel weniger Aufwand verursacht als jede andere Einstellungsoperation.
Die Implementierung von Clang verdient mehr Aufmerksamkeit, da sie erheblich schneller ist. Einige zusätzliche Tests zeigen, dass sein Memset tatsächlich auf Null-Nicht-Null-Memsets für 400-Byte-Arrays spezialisiert ist, die viel langsamer sind (~ 220 ms) und mit gccs vergleichbar sind. Das Memsetting ungleich Null mit einem 800-Byte-Array macht jedoch keinen Geschwindigkeitsunterschied, weshalb in diesem Fall das Memset wahrscheinlich eine schlechtere Leistung aufweist als meine Implementierung - die Spezialisierung gilt nur für kleine Arrays, und der Cuttoff liegt bei etwa 800 Byte. Beachten Sie auch, dass gcc 'fill' und 'ZERO' nicht für memset optimiert sind (unter Berücksichtigung des generierten Codes). Gcc generiert einfach Code mit identischen Leistungsmerkmalen.
Fazit: memset ist nicht wirklich für diese Aufgabe optimiert, so wie es die Leute vorgeben würden (andernfalls hätten das memset von gcc und msvc und llvm die gleiche Leistung). Wenn es auf die Leistung ankommt, sollte memset keine endgültige Lösung sein, insbesondere für diese umständlichen mittelgroßen Arrays, da es nicht auf das Löschen von Bits spezialisiert ist und nicht besser von Hand optimiert wird, als es der Compiler alleine tun kann.
quelle
a
in ein Register passen. Anschließend durchläuft er alle 32-Byte-Blöcke, die mit Zeigerarithmetik ((float *)((a)+x)
) vollständig überschrieben werden sollen . Die beiden Intrinsics (beginnend mit_mm256
) erstellen einfach ein mit Null initialisiertes 32-Byte-Register und speichern es im aktuellen Zeiger. Dies sind die ersten 3 Zeilen. Der Rest behandelt nur alle Sonderfälle, in denen der letzte 32-Byte-Block nicht vollständig überschrieben werden sollte. Aufgrund der Vektorisierung ist es schneller. - Ich hoffe das hilft.Von
memset()
:Sie können verwenden,
sizeof(myarray)
wenn die Größe vonmyarray
zur Kompilierungszeit bekannt ist. Andernfalls müssen Sie die Länge verfolgen , wenn Sie ein Array mit dynamischer Größe verwenden, z. B. übermalloc
odernew
.quelle
sizeof
immer zur Kompilierungszeit ausgewertet (und kann nicht mit VLAs verwendet werden). In C99 kann es sich bei VLAs um einen Laufzeitausdruck handeln.c
undc++
. Ich kommentierte Alex 'Antwort: "Sie können sizeof (myarray) verwenden, wenn die Größe von myarray zur Kompilierungszeit bekannt ist."Sie können verwenden
memset
, aber nur, weil unsere Auswahl an Typen auf integrale Typen beschränkt ist.Im Allgemeinen ist es in C sinnvoll, ein Makro zu implementieren
Auf diese Weise erhalten Sie C ++ - ähnliche Funktionen, mit denen Sie ein Array von Objekten eines beliebigen Typs auf Null zurücksetzen können, ohne auf Hacks wie zurückgreifen zu müssen
memset
. Grundsätzlich ist dies ein C-Analogon der C ++ - Funktionsvorlage, außer dass Sie das Typargument explizit angeben müssen.Darüber hinaus können Sie eine "Vorlage" für nicht verfallene Arrays erstellen
In Ihrem Beispiel würde es als angewendet
Es ist auch erwähnenswert, dass speziell für Objekte von Skalartypen ein typunabhängiges Makro implementiert werden kann
und
das obige Beispiel in verwandeln
quelle
;
nach dem weglassenwhile(0)
, damit man anrufen kannZERO(a,n);
, +1 tolle Antwortdo{}while(0)
Redewendung erfordert Nein;
in der Makrodefinition. Fest.Für die statische Deklaration könnten Sie Folgendes verwenden:
Für die dynamische Deklaration schlage ich den gleichen Weg vor:
memset
quelle
zero(myarray);
ist alles was Sie in C ++ brauchen.Fügen Sie dies einfach einem Header hinzu:
quelle
zero
auch z. B. korrektT=char[10]
ist, wenn dasarr
Argument ein mehrdimensionales Array ist, zchar arr[5][10]
.ARRAY_SIZE
Makro, das bei Verwendung in einem mehrdimensionalen Array die falsche Größe angibt. Ein besserer Name wäre vielleichtARRAY_DIM<n>_SIZE
.Hier ist die Funktion, die ich benutze:
Sie können es so nennen:
Oben ist mehr C ++ 11 als die Verwendung von Memset. Außerdem wird ein Fehler bei der Kompilierung angezeigt, wenn Sie ein dynamisches Array mit Angabe der Größe verwenden.
quelle