Wann Macro und wann eigene Funktion?



  • Ich habe eine C++ Klasse und versuche zzt teile der Funktionen in 64-bit selbst zus chreiben um selbst SSE einzuarbeiten. Das ganze Klappt soweit auch einwandfrei. Allerdings habe ich an vielen Stellen doppelten Code, der dadurch zu Stande kommt das viele Funktionen 2 Versionen haben, Version 1 bearbeitet die Instanz, Version 2 erstellt eine Kopie mit der dann Version 1 aufgerufen wird. Wenn ich jedoch mit SSE arbeite ist es für mich unerheblich das eine Kopie gemacht wird, es reicht für mich aus den Speicherplatz zu haben. Dadurch habe ich bei Version 2 natürlich 2 calls weniger aber dafür eben eine fast identische Kopie von Version 1.

    Nun habe ich 2 Möglichkeiten um dem mehrfach Code zu entgehen, ich könnte ein Macro schreiben und damit vermeidend as ich weietre calls bekomme und damit die Code dopplung rausnehmen, jedoch sidn Macros ja immer sone Sache. Ich könnte aber auch ne Hilfsfunktion bauen die einfach den Kernteil ausführt, dazu müsste sie aber die Calling Convention ignorieren und sichd arauf verlassen das Aufrufer 8die ja auch von mir stammen) sidn um das bereitstellen der Daten kümmern.

    Nun meine Frage, woran kann man am besten festmachen wann man lieber eine Funktion nimmt und wann man lieber ein Macro nimmt?



  • und wie hängt die Frage nun mit Assembler zusammen?



  • Meine Frage zielt darauf ab ob es eine gute Faustregel gibt wann man eher Macros und wann eher eigenständige Funktionen nehmen sollte. Das Beispiel diente nur zur verdeutlichung dessen was bei mir gerade anliegt und ich habe damit zzt die Wahl zwischen macros, die ann sehr lang wären oder eben Funktionen die sich aber nur Teilweise an dei Calling Convention halten würden damit sie arbeiten könnten (+ den damit verbundenen zusätzlichen Function call).



  • Ich würde mal sagen, questioner wollte dich eher darauf ansprechen, daß du im falschen Board gelandet bist 😉

    Zu deiner Frage: In C++ würde ich eher Inline-Funktionen verwenden für alles, was du ohne Hilfe des Präprozessors lösen kannst.



  • CStoll schrieb:

    Ich würde mal sagen, questioner wollte dich eher darauf ansprechen, daß du im falschen Board gelandet bist 😉

    Ne ich bin hier schon richtig, stelle nur gerade fest das ich hinter 64-bit das Wort Assembler vergessen habe.



  • Schau dir den Code im Hexeditor an, und mach das Überflüssige dann einfach weg: Hex Hex.



  • es ist nicht ersichtlich, was du genau machst:
    - welchen Compiler verwendest du
    - welchen Assembler verwendest du (inline?)
    - wo arbeitest du mit macros: Assembler oder Compiler
    - Dein Frage liest sich so, als würdest du SMC erstellen (= Gegenteil von Optimierung)



  • masm schrieb:

    - welchen Compiler verwendest du

    Visual Studio 2010 und damit MASM für 64-bit, aber das ist eig für die Frage nach ner Grudnregel unerheblich, da calls usw ja eig imemr gleich sind.

    masm schrieb:

    - welchen Assembler verwendest du (inline?)

    Inline Assembler is unter 64-bit nicht, da muß man allers selber machen.

    masm schrieb:

    - wo arbeitest du mit macros: Assembler oder Compiler

    Momentan noch nirgendwo deswegen ja auch meine Frage hier, ich hab die Wahl Macros oder Funtkionen kann mich aber eben nciht entscheiden, deswegen ja generell der Post.



  • Xebov schrieb:

    Visual Studio 2010 und damit MASM für 64-bit, aber das ist eig für die Frage nach ner Grudnregel unerheblich, da calls usw ja eig imemr gleich sind.

    wenn du das meinst, dann wird Es wohl so sein!
    ~goole.de: agner fog calling conventions~

    Ich würde an deiner stelle einfach eine Funktion (fastcall) schreiben, und diese dann an passender Stelle in der/den Methode/n aufrufen.
    Auserdem würde ich dir empfehlen jwasm zu benutzen ...



  • Danke. Werd mir beides mal anschaun. Du würdest also eine Funktion auch dann nutzen wenn sie sich nicht an Calling Conventions halten würde? das wäre heir der Fall weil sich uu das daten rückkopieren in die Aufrufer Funktion verlagern würde.



  • Xebov schrieb:

    Du würdest also eine Funktion auch dann nutzen wenn sie sich nicht an Calling Conventions halten würde? das wäre heir der Fall weil sich uu das daten rückkopieren in die Aufrufer Funktion verlagern würde.

    ???
    Was ist daran so schwer eine externe Funktion aus einer Methode heraus aufzurufen? Du wirst ja wohl noch den Zeiger auf einen Puffer oder direkt auf die zu bearbeiteten Daten übergeben können?



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


Anmelden zum Antworten