Gibt es ein memset() Template?
-
Meinst du std::fill?
edit: Zu spät.
Der Unterschied zwischen fill und uninitialized_fill ist der, dass fill erwartet, dass dort schon ein Objekt liegt, während uninitialized_fill das Objekt vor Ort konstruiert. Nutze was immer in deinem Fall angebracht ist.
-
Und dieses Setzen von Konstanten Werten ist der Teil deines Programms, der die Performance negativ beeinflusst?
Falls ja, dann hast du ein Problem, weil schneller als die Transferrate des Arbeitsspeichers wirst du eh nicht. Und das sollte
memsetschon ausschöpfen.Falls nein, - und da gehe ich jetzt mal von aus - Premature Optimization ist keine gute Idee.
-
ProgChild schrieb:
Und dieses Setzen von Konstanten Werten ist der Teil deines Programms, der die Performance negativ beeinflusst?
Falls ja, dann hast du ein Problem, weil schneller als die Transferrate des Arbeitsspeichers wirst du eh nicht. Und das sollte
memsetschon ausschöpfen.Falls nein, - und da gehe ich jetzt mal von aus - Premature Optimization ist keine gute Idee.
Du hast schon recht!
Meine Engine braucht braucht zu lange zum Starten, so dass es eine Verzögerung gibt, bevor der Benutzer arbeiten kann. Und das liegt an der Berechnung von KPK-Bitbases (Schach) und der Initialisierung von Hashspeichern und LUTs.Ist ausserdem "nur" Feinschliff.
-
Tomahawk schrieb:
Du hast schon recht!
Meine Engine braucht braucht zu lange zum Starten, so dass es eine Verzögerung gibt, bevor der Benutzer arbeiten kann. Und das liegt an der Berechnung von KPK-Bitbases (Schach) und der Initialisierung von Hashspeichern und LUTs.Ist ausserdem "nur" Feinschliff.
Und ein Profiler hat die jetzt
memsetals teuerste Funktion angezeigt? Kommt mir ein wenig seltsam vor, wenn dumemsetnur einmal für die Initialisierung verwendest. Bei heutigem Arbeitsspeicher kannst du mehr als ein GByte pro Sekunde schreiben.
-
Ach, das soll dasselbe sein wie memset, nur dass der zu schreibende Wert eine Compilezeitkonstante ist? Ich hatte das wohl falsch verstanden.
Es gibt in manchen Umgebungen Funktionen wie bzero (BSD-Welt) oder ZeroMemory (Windows-Welt), die dasselbe wie memset(p, 0, n) machen. Aber ob das wirklich schneller als reines memset ist? Erstens hätte man es dann doch in die Standardlibrary aufgenommen (memzero z.B.), zweitens ist memset für gute Compiler eine intrinsische Funktion, d.h. sie generieren dafür spezielle Codemuster, statt einen Aufruf zu einer Libraryfunktion. Damit sollte memset in den meisten(oder immer?) Fällen schon optimalen Code liefern.
-
ProgChild schrieb:
weil schneller als die Transferrate des Arbeitsspeichers wirst du eh nicht. Und das sollte
memsetschon ausschöpfen.Sollte ja, ist aber nicht immer so

MSVC + "use intrinsics = yes" =>rep stosb=> auf vielen CPUs relativ langsam
-
hustbaer schrieb:
ProgChild schrieb:
weil schneller als die Transferrate des Arbeitsspeichers wirst du eh nicht. Und das sollte
memsetschon ausschöpfen.Sollte ja, ist aber nicht immer so

MSVC + "use intrinsics = yes" =>rep stosb=> auf vielen CPUs relativ langsamNimnmt er auch rep, wenn die Größe zur Compilezeit unbekannt ist?
-
volkard schrieb:
hustbaer schrieb:
ProgChild schrieb:
weil schneller als die Transferrate des Arbeitsspeichers wirst du eh nicht. Und das sollte
memsetschon ausschöpfen.Sollte ja, ist aber nicht immer so

MSVC + "use intrinsics = yes" =>rep stosb=> auf vielen CPUs relativ langsamNimnmt er auch rep, wenn die Größe zur Compilezeit unbekannt ist?
Ich glaube ja, müsste ich aber nochmal ausprobieren.
Ich glaube sogar, dass er nichtmal bei sehr grossen konstanten Werten (wo die Setup-Kosten gering im Vergleich zu den Gesamt-Kosten sind) schlau genug war, die Funktionen aufzurufen. Obwohl die bei grossen Werten um ein vielfaches schneller sind als die intrinsics.
Ich weiss nur noch sicher, dass ich extra ein
#pragma function(memset, memcpy)in ein paar Source-Files reinmachen musste, damit er bei meinen Software-Blitting/Fill Funktionen nichtrep stosbbzw.rep movsbausmemset()bzw.memcpy()macht. Und da dabei die Grösse immer variabel war...
-
ProgChild schrieb:
Falls nein, - und da gehe ich jetzt mal von aus - Premature Optimization ist keine gute Idee.
Und wieso nicht? Wenn ich weiß, wie ich effizient Speicherbereiche initialisiere, dann gewöhne ich mir das so an, egal ob es in dem konkreten Fall hier was bringt oder nicht. So lange es nicht umständlicher zu schreiben/lesen ist als was anderes, sehe ich hier kein Problem mit 'premature optimization'. Die ist nämlich nur die 'root of all evil', wenn man unnötigerweise Optimierungen einbaut, die nachher die Erweiterbarkeit und Lesbarkeit des Codes beeinflussen. Aber wenn ich z.B. einmal weiß, dass std::fill doppelt so schnell ist wie eine Schleife (stimmt i.d.R. nicht, nur als Beispiel), dann nehme ich das doch.
-
Walli schrieb:
Und wieso nicht?
In diesem konkreten Fall, auf den ich mich hier beziehe, ist es keine gute Idee. Bevor ist
memsetschneller kriege, muss ich so viele plattform-spezifische Details berücksichtigen, dass ich mir sicher sein sollte, dass das wirklich mein Flaschenhals ist.Mehr sage ich nicht.
-
Das ist mal wieder Theorie vs. Praxis
Was hindert den VC++ eigentlich, auch bei intrinsics dword-weise (oder was gerade optimal ist) zu arbeiten?
-
Bashar schrieb:
Das ist mal wieder Theorie vs. Praxis
Was hindert den VC++ eigentlich, auch bei intrinsics dword-weise (oder was gerade optimal ist) zu arbeiten?Oops
Erstmal die Antwort: nichts hindert ihn, nur VC6 hat es IIRC nicht gemacht.Ich hätte vermutlich gleich dazuschreiben sollen dass ich das damals mit dem nunmehr sehr betagten VC6 getestet hatte

Gerade eben nochmal mit VC9 getestet, etwas ausführlicher, und VC9 ist viel viel schlauer

* mit
#pragma function(memset)macht VC9 wie auch VC6 brav nen Funktionsaufruf* mit
#pragma intrinsic(memset):
... bei kleinen konstanten Werten wird ein Haufenmovdraus (KEINrep-Dreck mehr!)
... bei grossen konstanten Werten wird trotzdem die Funktionmemsetaufgerufen
... bei variablen Werten wird *immer* die Funktionmemsetaufgerufen (auch wenn die variablen Werte immer klein sind und man mit PGO compiliert)Wobei der VC9 Optimizer auch schlau genug ist, die Intrinsic-Variante zu erzeugen, wenn eine Variable durch Inlining zu einem konstanten Ausdruch wird.
Noch ein paar Details:
* die Grenze scheint bei 10xmovzu liegen. 38 Byte => 9xmov DWORD+ 1xmov WORD, 39 Byte => memset, 40 Byte =>10x mov DWORD, 41 Byte => memset
* genau das selbe Verhalten ist beim Initialisieren von Stack-Arrays zu beobachten, nur dass beim Initialisieren von Stack-Arrays das#pragma intrinsic/functionkeine Rolle zu spielen scheint (d.h. Schwellenwert ist immer 10x mov)p.S.: bei variablen Werten auch mit intrinsic "optimal" zu arbeiten ist glaube ich schwierig, weil der Code den man dafür brauch (die ganzen Fallunterscheidungen) schnell recht gross wird.
-
Jetzt ist die Welt wieder in Ordnung.
Und std::fill mit 0 wandelt er hoffentlich auch in memset um?
Weil das wäre sehr schön, da nicht mehr an Mikrooptimierung denken zu müssen.
-
jain
std::fill für char/signed char/unsigned char = memset
(keine compiler magic, sondern spezialisierung von _Fill in der std.lib)std::fill für short/int/... mit kleiner konstanter grösse = unrolled loop als DWORD movs soweit möglich (unabhängig von der variablen-grösse, also auch 8x DWORD mov für 16 shorts)
hier scheinen andere regeln zu gelten wie für intrinsic memset, z.b. macht er da auch bis zu 16x mov vs. nur bis max. 10x mov bei memset. auch greift hier das #pragma function nicht (was ja auch vollkommen OK ist).std::fill für short/int/... mit grosser konstanter grösse = rep stos (bäh)
std::fill für short/int/... mit variabler grösse = einfacher loop (aber inlined)
p.S.: ob std::fill mit wert == 0 oder wert != 0 aufgerufen wird hat bei meinen versuchen keinen unterschied gemacht. z.B. std::fill für short wird entweder zu nem haufen DWORD movs oder zu nem rep stosw oder zu nem normalen movw loop. ganz egal ob wert == 0 oder wert == 1 oder wert == 0x0101.
p.p.S.: alles in allem würde ich sagen, dass man wohl trotzdem auf mikro-optimierungen verzichten kann. der code ist zwar nicht in jedem fall optimal, aber IMO gut genug für fast alle anwendungen.
p.p.p.S.: würde mich interessieren was GCC 4.x aus memset/std::fill macht
-
Vielen Dank.
Ich habe versucht, den relevaten Code zu finden und zu pasten. Hoffentlich habe ich getroffen.
std::fill für short/int/... mit grosser konstanter grösse = rep stos (bäh)
int main() { int arr[4096]; cout<<arr[5]<<'\n'; fill(arr,arr+4096,0); cout<<arr[5]<<'\n'; }Die cout sind nur, um den Nutzcode zu finden. Fast. Ohne das zweite wird alles wegoptimiert.
pxor %xmm0, %xmm0 leal 16(%esp), %eax leal 16400(%esp), %edx .p2align 4,,7 L3: movaps %xmm0, (%eax) addl $16, %eax cmpl %edx, %eax jne L3Der scheint da mit mmx zu spielen. Ich glaube, das ist gut.
int main() { int arr[4096]; int i; cin>>i; cout<<arr[5]<<'\n'; fill(arr,arr+i,0); cout<<arr[5]<<'\n'; }wird zu
pxor %xmm0, %xmm0 xorl %eax, %eax .p2align 4,,7 L5: movl %eax, %edx incl %eax sall $4, %edx cmpl %esi, %eax movaps %xmm0, (%ecx,%edx) jb L5struct Foo{ virtual ~Foo(){ } }; int main() { cout<<5<<'\n'; Foo arr[4096]; cout<<5<<'\n'; cout<<&arr<<'\n'; }wird zu
movl %ebx, %eax .p2align 4,,7 L8: movl $__ZTV3Foo+8, (%eax) movl $__ZTV3Foo+8, 4(%eax) movl $__ZTV3Foo+8, 8(%eax) movl $__ZTV3Foo+8, 12(%eax) addl $16, %eax cmpl %esi, %eax jne L8Vierfaches unwinding.
struct Foo{ int bar; Foo(){ bar=3; } virtual ~Foo(){ } }; int main() { cout<<5<<'\n'; Foo arr[4096]; cout<<5<<'\n'; cout<<&arr<<'\n'; }wird zu
movl %ebx, %eax .p2align 4,,7 L8: movl $__ZTV3Foo+8, (%eax) movl $3, 4(%eax) addl $8, %eax cmpl %esi, %eax jne L8Und jetzt char[variabel]:
int main() { char arr[4096]; int i; cin>>i; cout<<arr[5]<<'\n'; fill(arr,arr+i,0); cout<<arr[5]<<'\n'; }wird zu
pxor %xmm0, %xmm0 xorl %eax, %eax .p2align 4,,7 L5: movl %eax, %edx incl %eax sall $4, %edx cmpl %eax, %edi movaps %xmm0, (%ecx,%edx) ja L5 addl 24(%esp), %ecx movl 24(%esp), %edx cmpl %edx, 28(%esp) je L6 .p2align 4,,7 L9: movb $0, (%ecx) incl %ecx cmpl %ecx, %esi jne L9 L6:Mir scheint, es gibt GCC-Leute, die daran viel Spaß haben und Sachen machen, die normalerweise kein Arbeitgeber bezahllen würde.
Ups, memset vergessen hab.
int main() { char arr[4096]; int i; cin>>i; memset(arr,0,i); cout<<arr[5]<<'\n'; }wird zu
call _memsetint main() { char arr[4096]; memset(arr,0,4096); cout<<arr[5]<<'\n'; }wird zu
call _memsetmemset(arr,0,8);wird zu
movl $0, 23(%esp) movl $0, 27(%esp) movb $0, 31(%esp) movl $1, 8(%esp)memset(arr,0,32);wird zu
movl %edx, %ecx xorl %eax, %eax shrl $2, %ecx testb $2, %dl rep stosl je L5 movw $0, (%edi) addl $2, %edi L5: andl $1, %edx je L6 movb $0, (%edi) L6:Oh. Er entscheiden anhand der Größe, welcher Inline-Code reingeknallt werden soll. Hier sieht man, daß er gar nicht bemerkt hat, daß am Ende das movw und movb nicht nötig gewesen wären. Ab 2049 ints nimmt er call memset.
Mir scheint, memset zu nehmen ist kontraproduktiv! Der memset-Speicher wird nur noch als Byte-Speicher gesehen. Durch memset verliert er Alignment-Informationen und die Größenteilbarekeit auch. Verliert mmx.

-
memset(arr,i,0)memset(ptr, value, size), nicht umgekehrt

BTW: ich verwende gerne derartige hilfsfunktionen um bestimmte optimierungen zu verhindern:
(und gleichzeitig bestimmte stellen zu markieren)void __declspec(noinline) observe(void* ptr) { char dummy[123] = {}; void** pp = reinterpret_cast<void**>(&dummy[0]); pp[1] = ptr; // dank der führenden nullen in dummy erzeugt das OutputDebugStringA unten keine ausgabe ::OutputDebugStringA(dummy); // jetzt muss der compiler annehmen dass es einen alias für "ptr" gibt }Alternativ könnte man das vermutlich auch so schreiben:
volatile void* volatile g_ovservable_dummy = 0; void __declspec(noinline) observe(void* ptr) { g_ovservable_dummy = ptr; g_ovservable_dummy = 0; }
-
ProgChild schrieb:
Walli schrieb:
Und wieso nicht?
In diesem konkreten Fall, auf den ich mich hier beziehe, ist es keine gute Idee. Bevor ist
memsetschneller kriege, muss ich so viele plattform-spezifische Details berücksichtigen, dass ich mir sicher sein sollte, dass das wirklich mein Flaschenhals ist.Es geht mir doch gerade in keiner Weise drum ob es ein Flaschenhals ist. Aber, wenn man den 'premature optimization'-Spruch bringt, dann doch am besten in Situationen, wo er angebracht ist. Also wenn jemand 200 statt 2 Zeilen schreibt um 3 Takte unnötigerweise (da kein Flaschenhals) zu sparen. Ob std::fill, memset oder Schleife ist von der Komplexität her für jemanden, der den Code noch einmal anpacken muss, letztlich egal. Von daher nehme ich doch das, was schöner ist und ggf. sogar vielleicht netterweise auf vielen Plattformen einen Tacken schneller. Sicher wird er mit diesen 'Optimierungen' keinen Blumentopf gewinnen, aber er wird sich auch nicht großartig damit den Code verhunzen. Außerdem sagte er doch nachher selber, dass es sich hier um 'Feinschliff' handelt.
-
Walli schrieb:
Von daher nehme ich doch das, was schöner ist und ggf. sogar vielleicht netterweise auf vielen Plattformen einen Tacken schneller. Sicher wird er mit diesen 'Optimierungen' keinen Blumentopf gewinnen, aber er wird sich auch nicht großartig damit den Code verhunzen. Außerdem sagte er doch nachher selber, dass es sich hier um 'Feinschliff' handelt.
Wenn er nur ein ein
memsetdurch einfillersetzt, dann würde ich dir zustimmen. Aber wer sagt denn, dass das die Lösung ist? Ihm ist sein Programm zu langsam und wenn die Initialisierung des Speichers wirklich der Flaschenhals ist, kann man sich durchaus ein paar Techniken überlegen, die ein paar Zeilen Code mehr verursachen.