1. Optimierungsspielchen
-
So, camper, du meintest hier doch mal, dass du evtl. Spass an der Optimierung von Codeschnippseln haettest.
Da nehme ich dich doch mal beim Wort und poste hier ein weiteres Schnippselchen.
KA, ob das "interessant" ist, aber ueberarbeitet werden muss es.Es handelt sich dabei um eine Funktion aus meinem PMMOD, die den zusammengemixten output des samplers-Apparats von 32Bit auf 16Bit zur Ausgabe runterbricht und die VU-Bars berechnet (im Moment noch in ebx und edx).
Natuerlich kann sich jeder beteiligen und Verbesserungsvorschlaege koennen auch MMX-Codes enthalten - aber bitte nichts Neueres.Proc CPY16Stereo NEAR MaxClip equ 100h * 7FFFh MinClip equ 100h * -8000h ... @@Loop: ; signed output here... mov eax, [dword ptr esi] cmp eax, MinClip jl @@ClipLow0 cmp eax, MaxClip jg @@ClipHi0 sar eax, 8 mov [word ptr edi], ax sar eax, 8 jns @@VUOK0 neg eax @@VUOK0: add ebx, eax @@ClipDone0: mov eax, [dword ptr esi + 4] cmp eax, MinClip jl @@ClipLow1 cmp eax, MaxClip jg @@ClipHi1 sar eax, 8 mov [word ptr edi + 2], ax sar eax, 8 jns @@VUOK1 neg eax @@VUOK1: add edx, eax @@ClipDone1: add esi, 8 add edi, 4 dec ecx jnz @@Loop ret @@ClipLow0: mov [word ptr edi], 8000h add ebx, 128 jmp @@ClipDone0 @@ClipHi0: mov [word ptr edi], 7FFFh add ebx, 128 jmp @@ClipDone0 @@ClipLow1: mov [word ptr edi + 2], 8000h add edx, 128 jmp @@ClipDone1 @@ClipHi1: mov [word ptr edi + 2], 7FFFh add edx, 128 jmp @@ClipDone1 ENDP CPY16Stereo
Wie gesagt: Wo das her kommt, gibt's noch mehr. Da nimmt die Optimierungsarbeit aber teils noch ganz andere Ausmasse an.
-
auf den ersten Blick geht jedenfalls (wenn du MMX zulässt, liegen P6-Instruktionen nahe)
Proc CPY16Stereo NEAR MaxClip equ 100h * 7FFFh MinClip equ 100h * -8000h ... @@Loop: ; signed output here... mov eax, [dword ptr esi] cmp eax, MinClip cmovl eax, MinClip cmp eax, MaxClip cmovg eax, MaxClip sar eax, 8 mov [word ptr edi], ax sar eax, 8 jns @@VUOK0 neg eax @@VUOK0: add ebx, eax @@ClipDone0: mov eax, [dword ptr esi + 4] cmp eax, MinClip cmovl eax, MinClip cmp eax, MaxClip cmovg eax, MaxClip sar eax, 8 mov [word ptr edi + 2], ax sar eax, 8 jns @@VUOK1 neg eax @@VUOK1: add edx, eax @@ClipDone1: add esi, 8 add edi, 4 dec ecx jnz @@Loop ret ENDP CPY16Stereo
Das Clipping macht das Ganze etwas unhandlich für MMX aber vielleicht hab ich noch eine Idee. Auch könnte man wohl ebp parallel für [esi+4] einsetzen und so weniger stark von der Latenz abhängig sein.
-
Mhmh, sieht auf jeden Fall kuerzer aus. Mal sehen, ob mein Testrechner (Pentium MMX 233MHz) die frisst. Sollte aber.
Nur um es ueberhaupt mal erwaehnt zu haben: Das Ziel ist vor allem mehr Geschwindigkeit - verkleinerung ist netter Nebeneffekt.
Vor dem Hintergrund weiss ich nun nicht, ob 2 cmovs verglichen mit Spruengen unbedingt einen Vorteil bringen...?
Was ich vor allem noch angedacht hatte, war, edx und ebx frei zu machen (stattdessen Stack, oder irgendwelche anderen temp. Variablen benutzen), um damit eax zu entlasten... Denn das die ganze Zeit praktisch ohne Unterbrechung nur eax beackert wird, ist bestimmt nicht vorteilhaft.
-
pentium mmx ist nicht gut - p6 heißt P Pro und aufwärts
keine ahnung ob cmov schneller ist - vermutlich bleibt es sich gleich.
bei einem Pentium ist hier tatsächlich noch einiges möglich - da dieser ziemlich schlecht in out-of-order-ausführung ist, muss man da schon nachhelfen. Bei diesem Prozessor (oder nur P Pro/P II?) kommt noch ein stall durch den Zugriff auf ax hinzu. möglicherweise sollte man die ergebnisse für esi und esi+4 zusammenfassen und als ein 32bit write durchführen.
-
Der ganze Spaß mal mit MMX, obwohl es auf einem Pentium möglicherweise nicht viel bringt
Proc CPY16Stereo NEAR ... psubd mm7, mm7 @@Loop: movq mm0, [qword ptr esi] add esi, 8 psrad mm0, 8 movq mm1, mm7 packssdw mm0, mm0 movd [dword ptr edi], mm0 add edi, 4 psraw mm0, 8 pcmpgtw mm1, mm0 dec ecx pxor mm0, mm1 ; psubw mm0, mm1 ; neg punpcklwd mm0, mm7 paddd mm2, mm0 jnz @@Loop ; mm2 entspricht edx:ebx im original ret ENDP CPY16Stereo
oder auch
Proc CPY16Stereo NEAR ... psubd mm7, mm7 lea esi, [esi + 8 * ecx] lea edi, [edi + 4 * ecx] neg ecx @@Loop: movq mm0, [qword ptr esi + 8 * ecx] psrad mm0, 8 movq mm1, mm7 packssdw mm0, mm0 movd [dword ptr edi + 4 * ecx], mm0 psraw mm0, 8 pcmpgtw mm1, mm0 inc ecx pxor mm0, mm1 ; psubw mm0, mm1 ; neg punpcklwd mm0, mm7 paddd mm2, mm0 jnz @@Loop ; mm2 entspricht edx:ebx im original ret ENDP CPY16Stereo
Allerdings führen komplexe Adressierung auf älteren Prozessoren gelegentlich zu einer längeren Ausführungszeit. Kann also sein, dass das hier nichts bringt.
-
ach loop-unrolling um den faktor 2 ist sinnvoll mit mmx, so können wir einige anweisungen gleich auf 4 wörter anwenden:
Proc CPY16Stereo NEAR ... psubd mm7, mm7 @@Loop: movq mm0, [qword ptr esi] movq mm1, [qword ptr esi + 8] add esi, 16 psrad mm0, 8 psrad mm1, 8 packssdw mm0, mm1 movq mm1, mm7 movq [qword ptr edi], mm0 add edi, 8 psraw mm0, 8 pcmpgtw mm1, mm0 sub ecx, 2 pxor mm0, mm1 ; psubw mm0, mm1 ; neg movq mm1, mm0 punpcklwd mm0, mm7 punpckhwd mm1, mm7 paddd mm2, mm0 paddd mm2, mm1 jnz @@Loop ; mm2 entspricht edx:ebx im original ret ENDP CPY16Stereo
Damit dürfte das Ende der Fahnenstange einigermaßen erreicht sein. Logischerweise könnte man das Ganze auch mit SSE2 über volle 128bit machen. Ansonsten kann man je nach Prozessor vielleicht noch die Reihenfolge der Instruktionen etwas anpassen - ich hab das ja nicht getestet.
Nächtes Problem bitte
-
K, wunderbar. Wenn ich demnaechst Zeit finde, messe ich die Geschwindigkeit der Codes auf meinem Testrechner durch.
Dann wende ich mich dem naechsten Problem zu.Aber falls dir vorher langweilig werden sollte: Da das ganze auf jeden Fall auch auf aelteren Maschinen laufen soll, brauche ich so oder so noch eine x86-Version...
camper schrieb:
Bei diesem Prozessor (oder nur P Pro/P II?) kommt noch ein stall durch den Zugriff auf ax hinzu. möglicherweise sollte man die ergebnisse für esi und esi+4 zusammenfassen und als ein 32bit write durchführen.
Jo, moeglich. Muesste man schauen, ob sich das in entsprechend wenigen Befehlen realisieren laesst, damit sich das lohnt...
-
Nobuo T schrieb:
Aber falls dir vorher langweilig werden sollte: Da das ganze auf jeden Fall auch auf aelteren Maschinen laufen soll, brauche ich so oder so noch eine x86-Version...
Noch älter als Pentium MMX? Brauchst du das für ein Computermuseum?
wo liegt denn dann die untere Grenze? 386? 486? Im Übrigen sehe ich dann nicht viele Möglichkeiten. Um die bedingten Sprünge kommt man kaum herum. Lediglich die unbedingten kann man loswerden, wenn man einfach ein bisschen C&P betreibt.
-
camper schrieb:
Nobuo T schrieb:
Aber falls dir vorher langweilig werden sollte: Da das ganze auf jeden Fall auch auf aelteren Maschinen laufen soll, brauche ich so oder so noch eine x86-Version...
Noch älter als Pentium MMX? Brauchst du das für ein Computermuseum?
wo liegt denn dann die untere Grenze? 386? 486?
So in etwa.
Ich gehe einfach davon aus, dass niemand auf einer neueren Kiste noch ein DOS (oder Win9x) betreibt. Und da der PMMOD nunmal bis jetzt noch immer ein Programm fuer DPMI (immerhin inzwischen schon 32Bit) ist...
In der DOSBox habe ich auf einem 1.7GHz-Rechner (duerfte ca. einem ~30MHz P6 entsprechen) ~20 uninterpolierte Stereokanaele bei 44100Hz bearbeitet bekommen. Weniger als 486 duerfte demnach wirklich keinen Sinn mehr machen.
Und wer hat schon noch einen 386er bei sich in Betrieb?camper schrieb:
Im Übrigen sehe ich dann nicht viele Möglichkeiten. Um die bedingten Sprünge kommt man kaum herum. Lediglich die unbedingten kann man loswerden, wenn man einfach ein bisschen C&P betreibt.
Jup. Viel ist da nicht mehr drin... Bin gerade am ueberlegen: Hatte der 486 eigentlich schon sowas wie Befehlspipelines?
-
Nobuo T schrieb:
Jup. Viel ist da nicht mehr drin... Bin gerade am ueberlegen: Hatte der 486 eigentlich schon sowas wie Befehlspipelines?
ja, 5-stufig-. beim pentium ging dann 2-fach supersklar für einfache Integersachen - das könnten wir hier noch ausnutzen, aber die vergleiche sind ziemlich im weg. out-of-order gibts wohl erst ab dem P6.
Da fällt mir ein, wenn ecx nicht zu groß ist, kann man sich bei dem letzten mmx code das entpacken am ende sparen, also einfach
paddusw mm2, mm0
das läuft dann frühestens nach 8k-byte Eingangsdaten über - oder man nimmt eine doppelte schleife. Jedenfalls spart das noch einmal 4 instruktionen ein.
-
sagt mal, seit ihr in der Lage euch den Code anzuschaun und dann z.B. direkt den korrespondierenden C++ Code zu schreiben der die selbe wirkung hätte? ... oder seht ihr einfach die ASM Commands als solche und wisst welche Kombinationen\Segmente sich optimiert darstellen lassen?
Ich finde das schon sehr nice ... im Mittelalter hätte man euch auf dem Scheiterhaufen verbrannt