Wann Macro und wann eigene Funktion?
-
Die Funktion würde eine komplexe Berechnung durchführen deren Ergebnisse in den SSE Registern landen. Die Calling Convention sieht nicht vor das man Ergebnisse über mehrere SSE register zurückgibt. Die Daten würden aber vom Aufrufer benötigt. Somit müsste man sie um Konform zu sein erst rauskopieren um sie danach wieder reinzukopieren.
-
Warum musst du irgendwo irgendwas was hin und her kopieren? – implementier den Kompleten Algorithmus in einer Funktion. Über die Windows ABI musst du dir nur dann Gedanken machen, wenn dein ‚Herrschaftsbereich‘ verlassen wird (dieser ist zumindest in Assembler klar zu erkennen).
Es wäre wohl deutlich einfacher, wenn du einfach mal deinen code postest.
-
Ja glaub auch fast das das einfacher ist wenn sichs nicht generalisieren lässt.
Die Klasse für die ich die Funktionen in Assembler umsetze hat ca 15 Zwillingspaare. Dh Funktionen die eigentlichd as selbe Tun, jedoch andere Zielregister haben weil eine Funktion this verändert und die andere eine Kopie. Hier mal ein kleines Beispiel:
; MatrixSSE& operator-= (const MatrixSSE &rhs); ??ZMatrixSSE@bf@@QEAAAEAV01@AEBV01@@Z PROC Frame .endprolog mov rax, rcx movups xmm0, XMMWORD PTR [rcx] movups xmm1, XMMWORD PTR [rdx] subps xmm0, xmm1 movups XMMWORD PTR [rcx], xmm0 movups xmm0, XMMWORD PTR [rcx + 16] movups xmm1, XMMWORD PTR [rdx + 16] subps xmm0, xmm1 movups XMMWORD PTR [rcx + 16], xmm0 movups xmm0, XMMWORD PTR [rcx + 32] movups xmm1, XMMWORD PTR [rdx + 32] subps xmm0, xmm1 movups XMMWORD PTR [rcx + 32], xmm0 movups xmm0, XMMWORD PTR [rcx + 48] movups xmm1, XMMWORD PTR [rdx + 48] subps xmm0, xmm1 movups XMMWORD PTR [rcx + 48], xmm0 ret ??ZMatrixSSE@bf@@QEAAAEAV01@AEBV01@@Z ENDP ;----------------------------------------------------------- ; MatrixSSE& operator- (const MatrixSSE &rhs) const; ??GMatrixSSE@bf@@QEBA?AV01@AEBV01@@Z PROC Frame .endprolog mov rax, rdx movups xmm0, XMMWORD PTR [rcx] movups xmm1, XMMWORD PTR [r8] subps xmm0, xmm1 movups XMMWORD PTR [rdx], xmm0 movups xmm0, XMMWORD PTR [rcx + 16] movups xmm1, XMMWORD PTR [r8 + 16] subps xmm0, xmm1 movups XMMWORD PTR [rdx + 16], xmm0 movups xmm0, XMMWORD PTR [rcx + 32] movups xmm1, XMMWORD PTR [r8 + 32] subps xmm0, xmm1 movups XMMWORD PTR [rdx + 32], xmm0 movups xmm0, XMMWORD PTR [rcx + 48] movups xmm1, XMMWORD PTR [r8 + 48] subps xmm0, xmm1 movups XMMWORD PTR [rdx + 48], xmm0 ret ??GMatrixSSE@bf@@QEBA?AV01@AEBV01@@Z ENDP
Da das ganze ja doppelter Code ist will ich den raus haben, nun hab ich eben 2 Möglichkeiten. Die obere Funktion könnte einfach rdx->r8 und rcx->rdx kopieren und die untere Aufrufen, das würde diese Funktion etwas langsammer machen. Oder aber ich werfe die gesammte Funktion in ein Macro und lasse die Register eben ändern. Mein Problem ist das mir noch die Erfahrung fehlt wann man welche Variante bevorzugen sollte. Deswegen wolte ich eigentlich ne generalisierte Antwort wann man eher auf Macros und wann eher auf Funktionsaufrufe zurückgreifen sollte.
-
Die erste Funktion ist ja eine Spezialisierung der zweiten - d.h. wenn man die zweite Funktion mit rcx=rdx=[...] aufruft, erhält man das verhalten Ersterer.
Die Frage ist viel mehr, ob dich ein paar Bytes mehr Code stören (IMO lächerlich).
Worüber du dir wirklich Gedanken machen solltest, ist das alignment: du bremst SSE durch nicht ausgerichtet zugriffe deutlich aus - dein Bemühungen sollten doch nicht daran scheitern!
Sonstiges: Richte die Eintrittspunkte der Funktionen mittels align 16 aus. Sofern aligned Speicher vorliegt, währe besser:
1. subps reg,mem128 zu benutzen
2. die Befehle zu Blöcken zu 'bündeln':movdqa xmm0,... movdqa xmm1,... ... subps xmm0,... subps xmm2,... ... movdqa ...,xmm0 movdqa ...,xmm1
(kann man unter Anderem in Intels/AMD optimizaion manuals nachlesen)
-
masm schrieb:
Die erste Funktion ist ja eine Spezialisierung der zweiten - d.h. wenn man die zweite Funktion mit rcx=rdx=[...] aufruft, erhält man das verhalten Ersterer.
Die Frage ist viel mehr, ob dich ein paar Bytes mehr Code stören (IMO lächerlich).Das ist soweit richtig. Bei ner Testmessung war die 1. Funktion aber um 10% langsammer wenn man die Werte der Register angepasst und die 2. aufgerufen hat.
masm schrieb:
Worüber du dir wirklich Gedanken machen solltest, ist das alignment: du bremst SSE durch nicht ausgerichtet zugriffe deutlich aus - dein Bemühungen sollten doch nicht daran scheitern!
Das die unaligend Movements langsammer sind ist mir klar. Die sind allerdings deshalb drin weil das erzwingen des 16Byte alignments für die Klasse nen Seiteneffekt haben kann beim weiterarbeiten mit der Klasse an anderer Stelle und solange der nicht ausgeräumt ist muß es erstmal unaligend bleiben.
masm schrieb:
Sonstiges: Richte die Eintrittspunkte der Funktionen mittels align 16 aus.
Hier verstehe ich nciht so ganz worauf du hinaus willst.
masm schrieb:
Sofern aligned Speicher vorliegt, währe besser:
1. subps reg,mem128 zu benutzen
2. die Befehle zu Blöcken zu 'bündeln':movdqa xmm0,... movdqa xmm1,... ... subps xmm0,... subps xmm2,... ... movdqa ...,xmm0 movdqa ...,xmm1
(kann man unter Anderem in Intels/AMD optimizaion manuals nachlesen)
Den Teil werd ich mal genau nachlesen.
-
Xebov schrieb:
Bei ner Testmessung war die 1. Funktion aber um 10% langsammer wenn man die Werte der Register angepasst und die 2. aufgerufen hat.
Kannst die Zweite Funktion ja mal in eine wraper-Funktion packen (c++) und schauen, ob der Compiler diese wegoptimiert.
Xebov schrieb:
Das die unaligend Movements langsammer sind ist mir klar. Die sind allerdings deshalb drin weil das erzwingen des 16Byte alignments für die Klasse nen Seiteneffekt haben kann beim weiterarbeiten mit der Klasse an anderer Stelle und solange der nicht ausgeräumt ist muß es erstmal unaligend bleiben.
Wie speicherst du den die Matrix-Daten? - Speicher mit benötigte alignment bekommst du z.B. durch HeapAlloc().
align 16 ??GMatrixSSE@bf@@QEBA?AV01@AEBV01@@Z PROC Frame .endprolog mov rax, rdx movaps xmm0,OWORD ptr [rcx+0*16] movaps xmm1,OWORD ptr [rcx+1*16] movaps xmm2,OWORD ptr [rcx+2*16] movaps xmm3,OWORD ptr [rcx+3*16] subps xmm0,OWORD ptr [r8+0*16] subps xmm1,OWORD ptr [r8+1*16] subps xmm2,OWORD ptr [r8+2*16] subps xmm3,OWORD ptr [r8+3*16] movaps OWORD ptr [rdx+0*16],xmm0 movaps OWORD ptr [rdx+1*16],xmm1 movaps OWORD ptr [rdx+2*16],xmm2 movaps OWORD ptr [rdx+3*16],xmm3 ret ??GMatrixSSE@bf@@QEBA?AV01@AEBV01@@Z ENDP
-
masm schrieb:
Kannst die Zweite Funktion ja mal in eine wraper-Funktion packen (c++) und schauen, ob der Compiler diese wegoptimiert.
Ne da kriegt er nichts optimiert. Das Problem ist das er beim Wrapper automatisch anfängt auf dem Stack 64Byte für das Zwischenergebniss anzulegen so das am ende doppelt kopeirt wird. Die Lösung ist noch langsammer als das interne durchschleifen in Assembler.
masm schrieb:
Wie speicherst du den die Matrix-Daten? - Speicher mit benötigte alignment bekommst du z.B. durch HeapAlloc().
Die Daten werden ua in Buffern gespeichert für den Transport an die GPU. Da könnte es Probleme geben wenn Daten gemischt werden und es zu Paddings, denke aber das Problem liese sich lösen. 16Byte aligned bekommt man auch mit new.
masm schrieb:
align 16 ??GMatrixSSE@bf@@QEBA?AV01@AEBV01@@Z PROC Frame .endprolog mov rax, rdx movaps xmm0,OWORD ptr [rcx+0*16] movaps xmm1,OWORD ptr [rcx+1*16] movaps xmm2,OWORD ptr [rcx+2*16] movaps xmm3,OWORD ptr [rcx+3*16] subps xmm0,OWORD ptr [r8+0*16] subps xmm1,OWORD ptr [r8+1*16] subps xmm2,OWORD ptr [r8+2*16] subps xmm3,OWORD ptr [r8+3*16] movaps OWORD ptr [rdx+0*16],xmm0 movaps OWORD ptr [rdx+1*16],xmm1 movaps OWORD ptr [rdx+2*16],xmm2 movaps OWORD ptr [rdx+3*16],xmm3 ret ??GMatrixSSE@bf@@QEBA?AV01@AEBV01@@Z ENDP
Kannst du mir kurz sagen welchen Sinn der Alignment Befehl da hat?
-
Xebov schrieb:
Ne da kriegt er nichts optimiert. Das Problem ist das er beim Wrapper automatisch anfängt auf dem Stack 64Byte für das Zwischenergebniss anzulegen so das am ende doppelt kopeirt wird. Die Lösung ist noch langsammer als das interne durchschleifen in Assembler.
Das kann ich nicht nachvollziehen! - Du übergibst doch nur Zeiger!
Xebov schrieb:
Kannst du mir kurz sagen welchen Sinn der Alignment Befehl da hat?
Beschleunigt das laden des codes - dies geschieht, je nach Architektur, in 16 oder 32 Byte Blöcken entsprechender Ausrichtung.
Laut AMD&Intel sollte man das bei alle Sprungzielen machen (unter Vorbehalt) -> manuals.
-
masm schrieb:
Das kann ich nicht nachvollziehen! - Du übergibst doch nur Zeiger!
Der Compiler schaut aber nciht in die Assembler Funktion hinterm + und sieht somit nicht das ein Speicherplatz für den Zwischenwert des x+x nicht nötig wäre, somit erstellt er ihn, ruft die Funktion und kopiert danach die Daten um.
masm schrieb:
Beschleunigt das laden des codes - dies geschieht, je nach Architektur, in 16 oder 32 Byte Blöcken entsprechender Ausrichtung.
Laut AMD&Intel sollte man das bei alle Sprungzielen machen (unter Vorbehalt) -> manuals.Ich hab in beiden heute mal rumgelesen. Konnte aber zB das Zusammenfassen zu Blöcken oder das Nutzen von op reg, mem statt op reg, reg nirgendwo finden.
-
[quote="Xebov"]
masm schrieb:
Ich hab in beiden heute mal rumgelesen. Konnte aber zB das Zusammenfassen zu Blöcken oder das Nutzen von op reg, mem statt op reg, reg nirgendwo finden.
1. zu op reg,mem:
Intels otimazion manual: 3.4.2.1 Optimizing for Micro-fusion schrieb:
Assembly/Compiler Coding Rule 18. (ML impact, M generality) For improving
fetch/decode throughput, Give preference to memory flavor of an instruction over
the register-only flavor of the same instruction, if such instruction can benefit from
micro-fusion.The following examples are some of the types of micro-fusions that can be handled
by all decoders:
• All stores to memory, including store immediate. Stores execute internally as two
separate μops: store-address and store-data.
• All “read-modify” (load+op) instructions between register and memory, for
example:
ADDPS XMM9, OWORD PTR [RSP+40]
FADD DOUBLE PTR [RDI+RSI*8]
XOR RAX, QWORD PTR [RBP+32]
[...]2. zu den 'Blöcken': kann ich jetzt grade auch nicht ausfindig machen. Wird verm. im Zusammenhang mit Parallelisierung durch out-of-order execution erwähnt.