Anfängerprobleme mit inline ASM und FPU



  • camper schrieb:

    es gibt noch 1 oder 2 punkte, um das ganze zu optimieren: es fällt ja z.b. auf, dass die quadrate von real- und imaginärteil 2mal berechnet werden. einaml beim divergenztest und zum zweiten mal dann für den nächsten iterationsschritt. da kann man sicher noch sparen. eigentlich solltest du die FPU immer angezeigt bekommen. andere registersätze sind nat. nur verfügbar, wenn die maschine, auf der du debuggst, diese auch hat.

    Also, danke noch mal für eure Hilfe. Zu hause habe ich dann auch die ganze "Pracht" 😉 der Register anschalten können. Ich hatte es hier auch nur mit einem C# Projekt getestet.
    Mit dem Quadrat hast Du natürlich auch Recht! Aber man möge mir dies verzeihen 😉 ,ich bin nur ein einfacher ASM Anfänger.

    Ich habe den Code nach deinen Vorschlägen noch mal umgebaut und der sieht jetzt so aus:

    __asm
    			{
    				xor eax, eax
    				mov ecx, [miters]
    				mov [iter], ecx		// FPU Stack:
    
    				fld [z.im]			// z.im
                    fld [z.re]			// z.re, z.im
                    fld ST(0)           // z.re, z.re, z.im
                    fmul ST(0), ST(0)   // z.re^2, z.re, z.im
                    fld ST(2)           // z.im, z.re^2, z.re, z.im
                    fmul ST(0), ST(0)   // z.im^2, z.re^2, z.re, z.im
    
    				calcLoop:
    					// z.re = z.re*z.re - z.im*z.im + c.re;
    										// FPU Stack:                   
                        fsubp ST(1), ST(0)	// z.re^2-z.im^2, z.re, z.im
                        fadd [c.re]			// z.re^2-z.im^2+c.re, z.re, z.im
    
                		// z.im = z.re*z.im + z.re*z.im + c.im;
                        fld ST(2)           // z.im, z.re^2-z.im^2+c.re, z.re, z.im
                        fmul ST(0), ST(2)	// z.im*z.re, z.re^2-z.im^2+c.re, z.re, z.im
                        fadd ST(0), ST(0)	// z.im*z.re+z.im*z.re, z.re^2-z.im^2+c.re, z.re, z.im
                        fadd [c.im]			// z.im*z.re+z.im*z.re+c.im, z.re^2-z.im^2+c.re, z.re, z.im
    
    					// obersten beiden Elemente kopieren und letzten beiden Stackelemente löschen
    					fst ST(3)			// z.im*z.re+z.im*z.re+c.im, z.re^2-z.im^2+c.re, z.re, z.im*z.re+z.im*z.re+c.im
    					fxch				// z.re^2-z.im^2+c.re, z.im*z.re+z.im*z.re+c.im, z.re, z.im*z.re+z.im*z.re+c.im
    					fst ST(2)			// z.re^2-z.im^2+c.re, z.im*z.re+z.im*z.re+c.im, z.re^2-z.im^2+c.re, z.im*z.re+z.im*z.re+c.im
    
                        // (z.re*z.re + z.im*z.im)
                        fmul ST(0), ST(0)	// (z.re^2-z.im^2+c.re)^2, z.im*z.re+z.im*z.re+c.im, z.re^2-z.im^2+c.re, z.im*z.re+z.im*z.re+c.im
                        fxch				// z.im*z.re+z.im*z.re+c.im, (z.re^2-z.im^2+c.re)^2, z.re^2-z.im^2+c.re, z.im*z.re+z.im*z.re+c.im
    					fmul ST(0), ST(0)	// (z.im*z.re+z.im*z.re+c.im)^2, (z.re^2-z.im^2+c.re)^2, z.re^2-z.im^2+c.re, z.im*z.re+z.im*z.re+c.im
                        fst ST(1)			// (z.re^2-z.im^2+c.re)^2, (z.im*z.re+z.im*z.re+c.im)^2, (z.re^2-z.im^2+c.re)^2, z.re^2-z.im^2+c.re, z.im*z.re+z.im*z.re+c.im
                        fst ST(1)			// (z.im*z.re+z.im*z.re+c.im)^2, (z.re^2-z.im^2+c.re)^2, (z.im*z.re+z.im*z.re+c.im)^2, (z.re^2-z.im^2+c.re)^2, z.re^2-z.im^2+c.re, z.im*z.re+z.im*z.re+c.im
                        faddp ST(1), ST(0)	// (z.im*z.re+z.im*z.re+c.im)^2+(z.re^2-z.im^2+c.re)^2, (z.im*z.re+z.im*z.re+c.im)^2, (z.re^2-z.im^2+c.re)^2, z.re^2-z.im^2+c.re, z.im*z.re+z.im*z.re+c.im
    
                        fcom [divergSq]		// magnitudeSq fcom divergSq
                        fnstsw ax			// schreibe fpu flags in ax
                        sahf				// schreibe ax in cpu flags
                    jnc theEnd				// wenn CF=0 -> ST(0) < divergSq, dann stoppe
    					fstp ST(0)			// (z.im*z.re+z.im*z.re+c.im)^2, (z.re^2-z.im^2+c.re)^2, z.re^2-z.im^2+c.re, z.im*z.re+z.im*z.re+c.im
    					dec ecx				// ecx--
    				jnz calcLoop			// wenn ecx > 0, go on
    
    				// Werte aus Registern in Speicher schreiben und Stack aufräumen
                    theEnd:
                    	sub [iter], ecx
                    	fstp [magn]			// (z.im*z.re+z.im*z.re+c.im)^2, (z.re^2-z.im^2+c.re)^2, z.re^2-z.im^2+c.re, z.im*z.re+z.im*z.re+c.im
                    	fstp ST(0)			// (z.re^2-z.im^2+c.re)^2, z.re^2-z.im^2+c.re, z.im*z.re+z.im*z.re+c.im
    					fstp ST(0)			// z.re^2-z.im^2+c.re, z.im*z.re+z.im*z.re+c.im
    					fstp ST(0)			// z.im*z.re+z.im*z.re+c.im
    					fstp ST(0)			// clear!
    			}
    

    Also, vielen vielen Dank noch mal! Das hätte ich so alleine nicht hinbekommen.



  • Noch ein klitzekleine Frage. 😉

    Und zwar ist die ASM Routine gleich schnell wie das C-Derivat. 😞 Naja, wenigstens ist sie nicht langsamer. :p
    Der "Flaschenhals" des Ganzen scheint die Win32 API SetPixel Funktion zu sein, welche sich an die ASM Routine anschließt. Diese Funktion verbraucht über 3/4 der Gesamtlaufzeit. Deshalb würde ich gerne diese Funktion durch eine Eigene ersetzen. Die Win32 Funktion sieht ja so aus: SetPixel(Handle auf DC, x, y, Farbe). Leider weiß ich nicht genau wie der Speicher dort aufgebaut ist. Das Ganze wird ja nicht direkt in den GraKa Speicher geschrieben, sondern in ein MemDC. Aber das müßte ja eigentl. auch egal sein. Jedenfalls ist ein Handle auf ein DC ja nur ein Zeiger auf den Speicher, wo die Farbinfos liegen, richtig? Ich würde halt gerne mit "rep stosX" den Speicher über ASM füllen. Aber ich habe keine Ahnung in welcher Form dieser aufgebaut. Also, die Pixel von links nach rechts , dann die nächste Zeile..., oder von oben nach unten, dann die nächste Spalte ... Welche Farbtiefe, also wie breit der Speicherbereich für ein Pixel ist. Word(2^32 Farben), Byte?? ÄIch erzeuge mir den memDC über CreateCompatibleDC(...), somit ist der memDC genauso aufgebaut wie der GraKa Speicher oder?
    Und ob sich es überhaupt lohnt dies mit "rep stos" zu machen?

    Wenn die Frage hier deplatziert ist, teilt mir das bitte mit und ich werde es mal bei den Win32 API Leuten 😃 posten.

    Danke im voraus!


  • Mod

    nach CreateCompatibleBitmap musst du glaub ich noch den bitmapinfoheader ausfüllen, dann kommt CreateDIBSection und SelectObject; bitmaps sind dann zeile für zeile organisiert, wobei jede zeile auf 4 byte ausgerichtet ist; siese info ist aber mit vorsicht zu geniessen, besser erst noch ins MSDN schauen

    auf jeden fall ist die idee, zunächst ein bitmap zu füllen, eine sehr gute. und wenn du mutig bist, machst du alles (einschliesslich der winapi aufrufe) per inline assembler 😉

    im (z.re*z.re + z.im*z.im) teil ist dir glaub ich ein fehler unterlaufen, die beiden fst st(1) verstehe ich nicht

    man kann die schleife noch etwas weiter verbessern, divergSq und c sind ja invariant (zumindest per pixel, evtl. auch im gesamten bild, je nachdem, welche menge zu zeichnest), also ist es sinnvoll, diese bereits vor der schleife zu laden, das spart speicherzugriffe und umwandlungskosten (die fpu wandelt ja alles ins 80bit extended format um). dann kann man fcom auch wieder durch fcomp ersetzen, das spart dann ein fstp ein, ausserhalb der schleife muss man die addition dann eben nochmal ausführen, das ist ja vertretbar. evtl. könnte man es auch so machen, dass man re^2 direkt mit der summe überschreibt (also noch ein fxch einfügen), das fcom belässt, aber das fstp entfernt, und dann im nächsten durchlauf, den imaginärteil zweimal abzieht - das spart einmal fld/fstp, allerdings kommt eine zusätzliche subtraktion ins spiel, das muss man also abwägen (bzw. ausmessen - das ist immer das beste )
    3. generell lässt sich das ganze beschleunigen, wenn du bereit bist, genauigkeit zu opfern. also per _controlfp - inwieweit das machbar ist, hängt nat. von den voraussetzungen ab. eine reduzierung auf double (53bits) sollte aber meistens vertretbar sein. (diese massnmahme kann man im übrigen auch zusammen mit ganz normalen math operation benutzen - dort hilft es auch). 24bits für floats ist optimal, wenn nur geschwindigkeit gefragt ist.
    4. da du ja jetzt die quadrate nicht mehr doppelt berechnest, kannst du noch mehr sparen - die nicht quadrierten real/imaginärwerte brauchst du ja nur noch, um das produkt zu berechen, da kannst aber genausogut gleich nur das produkt speichern. (letzlich führst du die hälfte der berechnung der nächsten iteration aus, bevor die abbruchbedingung getestet wurde; diese form der optimierung kommt bei dieser art von problemen oft vor, denn man kann dann häufig einige instruktionen, die nur daten hin und her schieben, einsparen).



  • Mit DIB section usw. geht's natürlich, aber am einfachsten ist es wohl mit SetDIBitsToDevice. Da rechnest du z.B. eine ganze Zeile ins RAM und schreibst die dann mit einem einzigen Call auf den Bildschirm.



  • Danke für die Korrektur des Fehlers und die Hinweise. Echt stark!

    Hier der überarbeitete Code:

    __asm
    			{
    				xor eax, eax
    				mov ecx, [miters]
    				mov [iter], ecx		// FPU Stack:
    
    				fld [z.im]			// z.im
    				fmul [z.re]			// z.im*z.re
    				fld [z.re]			// z.re, z.im*z.re
    				fmul ST(0), ST(0)	// z.re^2, z.im*z.re
    				fld [z.im]			// z.im, z.re^2, z.im*z.re
    				fmul ST(0), ST(0)	// z.im^2, z.re^2, z.im*z.re
    
    				calcLoop:
    					// z.re = z.re*z.re - z.im*z.im + c.re;
    										// FPU Stack:
                        fsubp ST(1), ST(0)	// z.re^2-z.im^2, z.im*z.re
                        fadd [c.re]			// z.re^2-z.im^2+c.re, z.im*z.re
    
                		// z.im = z.re*z.im + z.re*z.im + c.im;
                        fxch ST(1)			// z.im*z.re, z.re^2-z.im^2+c.re
                        fadd ST(0), ST(0)	// z.im*z.re+z.im*z.re, z.re^2-z.im^2+c.re
                        fadd [c.im]			// z.im*z.re+z.im*z.re+c.im, z.re^2-z.im^2+c.re                
    
    					fld ST(1)			// z.re^2-z.im^2+c.re, z.im*z.re+z.im*z.re+c.im, z.re^2-z.im^2+c.re
    					fld ST(1)			// z.im*z.re+z.im*z.re+c.im, z.re^2-z.im^2+c.re, z.im*z.re+z.im*z.re+c.im, z.re^2-z.im^2+c.re
    					fmul ST(3), ST(0)	// z.im*z.re+z.im*z.re+c.im, z.re^2-z.im^2+c.re, z.im*z.re+z.im*z.re+c.im, z.re^2-z.im^2+c.re * z.im*z.re+z.im*z.re+c.im
    
                        // (z.re*z.re + z.im*z.im)
                        fmulp ST(2), ST(0)	// z.re^2-z.im^2+c.re, (z.im*z.re+z.im*z.re+c.im)^2, z.re^2-z.im^2+c.re * z.im*z.re+z.im*z.re+c.im
                        fmul ST(0), ST(0)	// (z.re^2-z.im^2+c.re)^2, (z.im*z.re+z.im*z.re+c.im)^2, z.re^2-z.im^2+c.re * z.im*z.re+z.im*z.re+c.im
                        fxch				// (z.im*z.re+z.im*z.re+c.im)^2, (z.re^2-z.im^2+c.re)^2, z.re^2-z.im^2+c.re * z.im*z.re+z.im*z.re+c.im
                        fst ST(1)			// (z.re^2-z.im^2+c.re)^2, (z.im*z.re+z.im*z.re+c.im)^2, (z.re^2-z.im^2+c.re)^2, z.re^2-z.im^2+c.re * z.im*z.re+z.im*z.re+c.im
                        fst ST(1)			// (z.im*z.re+z.im*z.re+c.im)^2, (z.re^2-z.im^2+c.re)^2, (z.im*z.re+z.im*z.re+c.im)^2, (z.re^2-z.im^2+c.re)^2, z.re^2-z.im^2+c.re * z.im*z.re+z.im*z.re+c.im
                      	faddp ST(1), ST(0)	// (z.re^2-z.im^2+c.re)^2+(z.im*z.re+z.im*z.re+c.im)^2, (z.im*z.re+z.im*z.re+c.im)^2, (z.re^2-z.im^2+c.re)^2, z.re^2-z.im^2+c.re * z.im*z.re+z.im*z.re+c.im
    
                        fcom [divergSq]		// magnitudeSq fcom divergSq
                        fnstsw ax			// schreibe fpu flags in ax
                        sahf				// schreibe ax in cpu flags
                    jnc theEnd				// wenn CF=0 -> ST(0) < divergSq, dann stoppe
    					fstp ST(0)			// (z.im*z.re+z.im*z.re+c.im)^2, (z.re^2-z.im^2+c.re)^2, z.re^2-z.im^2+c.re * z.im*z.re+z.im*z.re+c.im
    					dec ecx				// ecx--
    				jnz calcLoop			// wenn ecx > 0, go on
    
    				// Werte aus Registern in Speicher schreiben und Stack aufräumen
                    theEnd:
                    	sub [iter], ecx
                    	fstp [magn]			// (z.im*z.re+z.im*z.re+c.im)^2, (z.re^2-z.im^2+c.re)^2, z.re^2-z.im^2+c.re * z.im*z.re+z.im*z.re+c.im
                    	fstp ST(0)			// (z.re^2-z.im^2+c.re)^2, z.re^2-z.im^2+c.re * z.im*z.re+z.im*z.re+c.im
    					fstp ST(0)			// z.re^2-z.im^2+c.re * z.im*z.re+z.im*z.re+c.im
    					fstp ST(0)			// clear!
    			}
    

    Besser so? 🙂

    Noch ne Frage zu dem _controlfp. Ich habe mal in die MSDN geschaut. Wenn ich diesen Aufruf hier mache: "_control87( _PC_24, MCW_PC )", dann setze ich die Genauigkeit auf 24 Bit herunter, richtig? Gilt dies solange bis ich es wieder auf einen anderen Wert setze / das Proggi beende?

    Ich habe in dem Referenzteil in meinem ASM Buch von Oliver Müller gelesen, dass es angeblich genauso lange dauert, Operationen wie fadd z.B direkt mit Speicherinhalten zu machen oder mit dem Stack. Mir scheint das aber auch nicht so recht einzuleuchten. Ich werde die 3 Varis deshalb: diverg, c.im, c.re mit in den Stack schmeißen.

    Also meinst Du, dass ich das Handle der Bitmap zum zeichnen benutzen soll und nicht das Handle des memDc? Müsste doch eigentlich egal sein oder? Denn schließlich wähle ich ja die Bmp vorher in den memDC ein und somit zeigt der ja auf den gleichen Bereich oder versteh ich da etwas falsch?

    Also, danke noch mal.



  • Wenn du bei jedem Pixel einen GDI-Aufruf machst, ist es ziemlich egal, wie schnell deine Berechnung ist. Daher musst du mit einer Bitmap arbeiten.

    Die FPU-Genauigkeit bleibt bis zum nächsten _controlfp oder bis zum Ende des Programms so eingestellt.

    Es mag sein, dass es auf manchen CPUs keinen Geschwindigkeitsnachteil bringt, aus dem Speicher zu laden. Darauf verlassen würde ich mich aber nicht.


  • Mod

    Besser so? 🙂

    das problem mit den fehlerhaften fst st(1) hast du noch nicht beseitigt. ich glaube dort müsste ein fld stehen, allerdings ist es schwierig, deinen kommentaren zu folgen (den stack zustand zu dokumentieren ist sehr gut) - ich schlage vor die ausdrücke zu vereinfachen: z. wird weggelassen, also nur re bzw. im; bei quadraten entfällt das ^ und dann folgende ausdrücke, sobald sie entstehen:
    nre = re2-im2+c.re (für neues oder next re)
    nim_2 = re*im
    nim = 2*re*im+c.im
    so wäre der zustand des stacks viel leichter zu überschauen.

    Noch ne Frage zu dem _controlfp. Ich habe mal in die MSDN geschaut. Wenn ich diesen Aufruf hier mache: "_control87( _PC_24, MCW_PC )", dann setze ich die Genauigkeit auf 24 Bit herunter, richtig? Gilt dies solange bis ich es wieder auf einen anderen Wert setze / das Proggi beende?

    prinzipiell bleibt das controlwort bestehen, solange es nicht überschrieben wird oder ein finit erfolgt. theoretisch sollte eine funktion, die das control word ändert, das auch dokumentieren, mit den normalen math routinen gibt es afaik jedenfalls keine probleme, es ist also eine permanente änderung.

    Ich habe in dem Referenzteil in meinem ASM Buch von Oliver Müller gelesen, dass es angeblich genauso lange dauert, Operationen wie fadd z.B direkt mit Speicherinhalten zu machen oder mit dem Stack. Mir scheint das aber auch nicht so recht einzuleuchten. Ich werde die 3 Varis deshalb: diverg, c.im, c.re mit in den Stack schmeißen.

    die aussage ist insofern richtig, als die zusätzliche latenz durch speicherzugriffe häufig durch die pipeline verdeckt wird, in schleifen, insbesondere wenn sie kurz sind, muss das aber nicht unbedingt gelten. jedenfalls mal ein zitat:
    IA-32 Intel Architecture Optimization Reference Manual:
    Floating-point, MMX technology, Streaming SIMD Extensions and Streaming SIMD Extension 2 instructions with load operations require 6 more clocks in latency than the register-only version of the instructions, but throughput remains the same.
    das bezieht sich auf den P4; bei athlons sind es 2 takte (siehe Software Optimization Guide for AMD Athlon 64 and AMD Opteron Processors) - beides übrigens sehr empfelenswerte lektüre. bei komplexen operationen, die gerne den ganzen stack benutzen würden, kann es tatsächlich eine gute idee sein, einzelne konstanten direkt aus dem speicher zu holen, wenn man dadurch die registerlast reduzieren kann.

    Also meinst Du, dass ich das Handle der Bitmap zum zeichnen benutzen soll und nicht das Handle des memDc? Müsste doch eigentlich egal sein oder? Denn schließlich wähle ich ja die Bmp vorher in den memDC ein und somit zeigt der ja auf den gleichen Bereich oder versteh ich da etwas falsch?

    Da fragst du am besten einen von den freundlichen winapi experten 🙂

    wenn sich diverSq in einem register befindet, kannst du auch fcomi bzw. fcomip einsetzen (setzt mindestens P2 bzw. PPro voraus), damit entfällt dann das fnstsw und sahf - es ist allerdings sinnvoll, ein paar instruktionen for dem jnc einzuschieben, um fehlerhafte sprungvorhersagen zu minimieren - dec ecx z.b. ist hier ungefährlich, da dec das carry flag nicht verändert. auch zwischen den beiden sprungbefehlen sollten sich weitere befehle befinden, da der throughput von sprüngen nicht sehr hoch ist.



  • Ich muss mich immer wieder bei euch bedanken, wie schnell und kompetent ihr mir hier helft! Super! Ich hab echt ne ganze Menge gelernt. Big THX !!!

    Ich hatte heute in der Mittagspause schnell ein bisschen an dem Code bebastelt, da hatte ich den Fehler mit dem fst wohl übersehen. Ist natürlich unsinnig, ich will ja die Daten kopieren und keine überschreiben.

    Ansonsten sieht der Code jetzt wie folgt aus:

    Aber die Laufzeit ist auf die Millisekunde genau gleich geblieben. 😞 Deswegen werde ich mich wohl morgen um die SetPixel Funktion kümmern. Aber für heute ist erst mal Schluss. 🙂 Schließlich ist ja Fr. 😃

    Also, schönen Abend noch.

    camper schrieb:

    ...beides übrigens sehr empfelenswerte lektüre...

    Das von Intel hab ich mir schon als CD geordert. 🙂 Das von AMD muss ich mir auch noch besorgen, wenn ich mal die Muse habe mich auf der Homepage umzuschauen. 😮

    wenn sich diverSq in einem register befindet, kannst du auch fcomi bzw. fcomip einsetzen (setzt mindestens P2 bzw. PPro voraus), damit entfällt dann das fnstsw und sahf - es ist allerdings sinnvoll, ein paar instruktionen for dem jnc einzuschieben, um fehlerhafte sprungvorhersagen zu minimieren - dec ecx z.b. ist hier ungefährlich, da dec das carry flag nicht verändert. auch zwischen den beiden sprungbefehlen sollten sich weitere befehle befinden, da der throughput von sprüngen nicht sehr hoch ist.

    Das versteh ich nicht ganz. Was sollte ich denn für Befehle dort dazwischen "rein packen"? 😕

    Ich muss mich immer wieder bei euch bedanken, wie schnell und kompetent ihr mir hier helft! Super! Ich hab echt ne ganze Menge gelernt. Big THX !!!

    Ich hatte heute in der Mittagspause schnell ein bisschen an dem Code bebastelt, da hatte ich den Fehler mit dem fst wohl übersehen. Ist natürlich unsinnig, ich will ja die Daten kopieren und keine überschreiben.

    Ansonsten sieht der Code jetzt wie folgt aus:

    __asm
    {
        xor eax, eax
        mov ecx, [miters]
        mov [iter], ecx     // FPU Stack:
    
        fld [c.im]          // c.im
        fld [c.re]          // c.re, c.im
        fld [divergSq]      // div, c.re, c.im
        fld [z.im]          // z.im, div, c.re, c.im
        fmul [z.re]         // z.im*z.re, div, c.re, c.im
        fld [z.re]          // z.re, z.im*z.re, div, c.re, c.im
        fmul ST(0), ST(0)   // z.re2, z.im*z.re, div, c.re, c.im
        fld [z.im]          // z.im, z.re2, z.im*z.re, div, c.re, c.im
        fmul ST(0), ST(0)   // z.im2, z.re2, z.im*z.re, div, c.re, c.im
    
        calcLoop:
            // z.re = z.re*z.re - z.im*z.im + c.re;
                                // FPU Stack:
            fsubp ST(1), ST(0)  // z.re2-z.im2, z.im*z.re, div, c.re, c.im
            fadd ST(0), ST(3)   // z.re2-z.im2+c.re = nre, z.im*z.re, div, c.re, c.im
    
            // z.im = z.re*z.im + z.re*z.im + c.im;
            fxch ST(1)          // z.im*z.re, nre, div, c.re, c.im
            fadd ST(0), ST(0)   // z.im*z.re+z.im*z.re, nre, div, c.re, c.im
            fadd ST(0), ST(4)   // z.im*z.re+z.im*z.re+c.im = nim, nre, div, c.re, c.im   
    
            // nre * nim
            fld ST(1)           // nre, nim, nre, div, c.re, c.im
            fld ST(1)           // nim, nre, nim, nre, div, c.re, c.im
            fmul ST(3), ST(0)   // nim, nre, nim, nre * nim, div, c.re, c.im
    
            // nz.re*nz.re , nz.im*nz.im
            fmulp ST(2), ST(0)  // nre, nim2, nre * nim, div, c.re, c.im
            fmul ST(0), ST(0)   // nre2, nim2, nre * nim, div, c.re, c.im
    
            // (nz.re2 + nz.im2)
            fxch ST(1)          // nim2, nre2, nre * nim, div, c.re, c.im
            fld ST(1)           // nre2, nim2, nre2, nre * nim, div, c.re, c.im
            fld ST(1)           // nim2, nre2, nim2, nre2, nre * nim, div, c.re, c.im
            faddp ST(1), ST(0)  // nre2+nim2, nim2, nre2, nre * nim, div, c.re, c.im
    
            fcom ST(4)          // magnitudeSq fcom divergSq
            fnstsw ax           // schreibe fpu flags in ax
            sahf                // schreibe ax in cpu flags
            dec ecx             // ecx--
        jnc theEnd              // wenn CF=0 -> ST(0) < divergSq, dann stoppe
            fstp ST(0)          // nim2, nre2, nre * nim, div, c.re, c.im
        jnz calcLoop            // wenn ecx > 0, go on
    
        // Werte aus Registern in Speicher schreiben und Stack aufräumen
        theEnd:
            sub [iter], ecx
            fstp [magn]         // nim2, nre2, nre * nim, div, c.re, c.im
            fstp ST(0)          // nre2, nre * nim, div, c.re, c.im
            fstp ST(0)          // nre * nim, div, c.re, c.im
            fstp ST(0)          // div, c.re, c.im
            fstp ST(0)          // nc.re, c.im
            fstp ST(0)          // c.im
            fstp ST(0)          // clear!
    }
    

    Aber die Laufzeit ist auf die Millisekunde genau gleich geblieben. 😞 Deswegen werde ich mich wohl morgen um die SetPixel Funktion kümmern. Aber für heute ist erst mal Schluss. 🙂 Schließlich ist ja Fr. 😃

    Also, schönen Abend noch.



  • Du weißt aber schon, dass man die PDFs auch saugen kann?



  • Ringding schrieb:

    Du weißt aber schon, dass man die PDFs auch saugen kann?

    Jupp. 🙂 Aber wieso soll ich mit meiner schmalbandigen Verbindung 😉 die PDFs saugen, wenn Intel so freundlich ist und sie einem zuschickt. 👍



  • Ich hatte ganz vergessen euch mitzuteilen, dass es dann alles ordenlich geklappt hatte und das Programm zieml. gut funktioniert. Durch das direkte kopieren in den Speicherbereich der DibSection arbeitet der Algorithmus jetzt 9-10 mal schneller. Wen es interessiert, der kann sich das Ganze ("Fractal Set Viewer") von meiner Homepage runterladen.

    Vielen Dank noch mal!


  • Mod

    sehr schön. natürlich ist noch lange nicht das ende der fahnenstange, was die optimierung betrifft, erreicht 🙂
    ich hätte da ein paar ideen, was man versuchen könnte:

    1. sse statt fpu - da gibt es prinzipiell zwei möglichkeiten,
    - man arbeitet mit ungepackten daten. hier könnte man evtl. eine bessere auslastung der ausführungseinheiten des prozessors erreichen, die register sind ja nicht als stack angelegt, so dass man nicht ständig mit stackumorganisieren beschäftigt ist
    - mit gepackten daten: also 4 floats mit einer instruktion. nachteil ist, dass man sich hier erst einmal gedanken machen muss, wie man mit fällen zurecht kommt, bei denen nur einige aber nicht alle der punkte divergieren. hier müsste man irgendwie die punkte auf die register mappen. am besten aber erst mal eine studie in c/c++ schreiben, um das konzept zu prüfen; auf jeden fall gäbe es eine menge overhead, ein geschwindigkeitsfaktor 2 könnte aber drin sein
    2. integer statt fpu. gerade beim mandelbrot set bietet sich unter umständen festkommaarithmetik an, da durch die addition des komplexen C der iterationswert ohnehin ständig normalisiert ist. es ergeben sich aber möglichweise etwas andere rundungsfehler.
    3. kombiniere integer mit fpu oder sse - integer und fpu/sse-befehle können ja weitestgehend parallel und unabhängig ausgeführt werden. hier wäre also auch noch potential vorhanden.


Anmelden zum Antworten