Ich saß gestern den gesamten Abend dran endlich meine Berechnung in ASM zu machen statt purem AutoIt und es hat sich rausgestellt,
dass meine Implementation gerade mal einen Vorteil von x1.28 bringt.
Natürlich bin ich kein ASM-Profi und der Code ist sicherlich noch weiter optimierbar, also tobt euch mal bitte aus und hilft mir, den Code zu optimieren!
Ich habe keine Funktion zum Berechnen der dritten Wurzel gefunden, also habe ich mir eine Approximation geschrieben.
Idee der Approximation
Approximation von einem Dezimalbruch N
durch Addition von 1/(2^x), x in Natürlichen Zahlen
Im x86-Befehlssatz habe ich zum Potenzieren keine wirklichen Funktionen gefunden bis auf F2XM1.
F2XM1 berechnet (2^x)-1 und da ich x^(1/3) rechnen musste bietet sich da die Umrechnungsformel x^y = 2^(y * log2(x)) an,
allerdings darf beim F2XM1 Befehl der Exponent x nur zwischen -1.0 und +1.0 liegen.
Damit konnte ich leider nicht den gesamten Wertebereich von 0.0 bis 1.0 hoch 1/3 abdecken -> 2^(1/3 * log2(0.0 bis 1.0)) da bei einem x von 0.125 (wird zu -1) und 1.0 (wird zu 0) abdecken.
Das reichte mir leider immer noch nicht, da ich gerne eine Abdeckung von 0.0 (wird zu -unendlich) bis 1.0 wollte.
Dann kam mir die Idee, einfach den Exponenten mittels der Wurzelfunktion zu approximieren.
Es gibt im x86-Befehlssatz die Funktion FSQRT und diese berechnet die Quadratwurzel von ST(0) (oberster Wert auf dem FPU-Stack).
Die Potenzgesetze erlauben es einem den Exponent zu addieren, wenn man Potenzen gleicher Basen multiplziert.
Beispiel: 2^(1/2) * 2^(1/4) = 2^(1/2 + 1/4) = 2^(3/4)
Jetzt kann man sich immer näher an den gewünschten Wert mittels Quadratwurzeln rantasten:
Beispiel: 1/6 = 0.166666666
Approximationsanfang = 0
1/2^0 = 1/1 = 1 1 + Approximation <= 1/6 ? Nein!
1/2^1 = 1/2 = 0.5 0.5 + Approximation <= 1/6 ? Nein!
1/2^2 = 1/4 = 0.25 0.25 + Approximation <= 1/6 ? Nein!
1/2^3 = 1/8 = 0.125 0.125 + Approximation <= 1/6 ? Ja! 0.125 + 0 = 0.0125 => Neue Approximation = 0.125
1/2^4 = 1/16 = 0.0625 0.0625 + Approximation <= 1/6 ? Nein!
1/2^5 = 1/32 = 0.03125 0.03125 + Approximation <= 1/6 ? Ja! 0.03125 + 0.125 = 0.15625 => Neue Approximation = 0.15625
1/2^6 = 1/64 = 0.015625 0.015625 + Approximation <= 1/6 ? Nein!
1/2^7 = 1/128 = 0.0078125 0.0078125 + Approximation <= 1/6 ? Ja! 0.15625 + 0.0078125 = 0.1640625 => Neue Approximation = 0.1640625
Wenn wir nun 0.1640625 als Approximation nutzen wollen müssen wir lediglich folgende Rechnung durchführen:
x^(1/8) * x^(1/32) * x^(1/128)
Als ASM-Code ungefähr so zu implementieren:
x -> ST0 | ST0 = x
SQRT(ST0) -> ST0 | ST0 = x^(1/2)
SQRT(ST0) -> ST0 | ST0 = (x^(1/2))^(1/2) = x^((1/2) * (1/2)) = x^(1/4)
SQRT(ST0) -> ST0 | ST0 = x^(1/8)
Wir haben nun einen Wert den wir für die Approximation brauchen also kopieren wir ST0 nach ST1
FST(ST1) | ST0 = ST0, ST1 = ST0
SQRT(ST0) -> ST0 | ST0 = x^(1/16)
SQRT(ST0) -> ST0 | ST0 = x^(1/32)
Neuer benötigter Wert!
FST(ST2) | ST0 = x^(1/32), ST1 = x^(1/8)
SQRT(ST0) -> ST0 | ST0 = x^(1/64)
SQRT(ST0) -> ST0 | ST0 = x^(1/128)
Neuer benötigter Wert steht in ST0.
Wir haben die Approximatino nun ausgerechnet und müssen nur noch die Ergebnisse multiplizieren:
FMULP | ST0 = x^(1/128) * x^(1/32), ST1 = x^(1/8) FMULP popt nämlich das Register nach der Multiplikation
FMULP | ST0 = x^(1/128) * x^(1/32) * x^(1/8)
Fertig gerechnet, das Ergebnis steht in ST0!
Man kann das ganze natürlich in ASM so programmieren, dann man beliebig nah an die Approximation kommt (wie das AutoIt-Äquivalent unten)
aber in meinem Code haben vier Faktoren als Genauigkeit ausgereicht also habe ich es hardgecodet und nicht dymanisch.
Momentan übergebe ich fast alle Daten mittels DllStructs an den ASM-Code und lese diese auch so wieder aus (DllStructGetData).
Gibt es da eine schnellere Möglichkeit als jeden Index einzeln aus dem Struct zu lesen?
Die "Zyklen pro Sekunde"-Messung schwankt sehr stark je nach Bildschirmauflösung und Visualizergröße.
Das Script liest aus dem fftBuffer aus der aktuellen .mp3-Datei und berechnet einen Visualizer welcher mittels GDI+ angezeigt wird.
Das Script zieht bei mir rund 15-20% CPU-Last (Intel Core i7 4790) und berechnet 260 Zyklen die Sekunde.
Am Ende soll ein Sleep in die Schleife eingebaut werden, damit nur noch 40-50 Zyklen pro Sekunde berechnet werden (sieht immer noch flüssig aus) und die CPU-Last dann auf <1% sinkt.
Das klappt auch bei mir schon fast mit einem Sleep(15) (42-44 Zyklen): CPU-Last: ~2% (mit anderen Prozessen im Hintergrund die nichts berechnen).
Dann wäre noch die Frage:
Ist es möglich die _GDIPlus_PathAddLine2 aus dem ASM-Code aufzurufen? Dann könnte ich nämlich die gesamte Berechnung der Punkte auf den ASM-Code auslagern und hätte dann nochmal einen Geschwindigkeitsboost! Wäre nett, wenn mir jemand zeigen könnte (wenn es möglich ist) wie es geht, die Implementierung würde ich gerne selber ausprobieren!!
Angehangen ist AssembleIt2_64, Approximation.au3, BASS.au3, BASSconstants.au3, bass.dll, visualizer.au3
Tobt euch aus!