Einzelnen Beitrag anzeigen

Benutzerbild von Khabarakh
Khabarakh

Registriert seit: 18. Aug 2004
Ort: Brackenheim VS08 Pro
2.876 Beiträge
 
#1

[ASM] Power-Geheimnisse

  Alt 21. Aug 2007, 02:17
Eigentlich wollte ich meine Kiste vor dreieinhalb Stunden abstürzen lassen herunterfahren, doch angeregt hiervon wollte ich mir einmal kurz die Power-Implementierung der RTL anschauen. Bei "kurz" ist es zwar nicht ganz geblieben, aber ich darf nun behaupten, den Code verstanden zu haben; mit der Zeit bin ich ebenfalls sehr zufrieden, denn es gab da ein kleines Problem: Ich beherrsche kein Wort Assembler [1] . Ausgestattet allein mit dem Verständnis des abstrakten Stack-Typs (soll heißen: Push und Pop ) und einer Webseite über FPU-Instruktionen habe ich mich also debuggend und kommentierend durch den Asm-Code gequält. Das Resultat sieht so aus [2]:
Delphi-Quellcode:
function Power(const Base, Exponent: Extended): Extended;
const
  Max : Double = MaxInt;
var
  IntExp : Integer;
asm
  // Kommentare stellen den FPU-Stack dar: // ST0, ST1
  fld Exponent // b
  [...]//schnipp
  fld Base // a, b
  [...]// schnipp
  fldln2 // ln 2, a, b
  fxch // a, ln 2, b
  fyl2x // ln 2 * ld a, b
  fxch // b, ln 2 * ld a
  fmulp st(1), st // ln 2 * ld a * b
  fldl2e // ld e, ln 2 * ld a * b
  fmulp st(1), st // ld e * ln 2 * ld a * b = ld a * b = x
  fld st(0) // x, x
  frndint // Round(x), x
  fsub st(1), st // Round(x), x - Round(x) // sagen wir einfach Int(x) und Frac(x), auch wenn wir ja auch aufgerundet haben könnten // siehe auch [url]http://t-a-w.blogspot.com/2006/06/docking-assembly.html[/url]
  fxch st(1) // Frac(x), Int(x)
  f2xm1 // 2^Frac(x) - 1, x
  fld1 // 1, 2^Frac(x) - 1, x
  faddp st(1), st // 1 + 2^Frac(x)-1 = 2^Frac(x)
  fscale // 2^Frac(x) * 2^Int(x) = 2^z = 2^(ld a * b) = a^b
end;
Das Ergebnis stimmt, trotzdem müsst ihr mir noch beim Füllen zweier Verständnislücken assistieren: Denn die Funktion zweier Teile habe ich zwar begriffen, doch ... kommen mir sie einfach überflüssig vor .

I
Delphi-Quellcode:
  fxch // b, ln 2 * ld a
  fmulp st(1), st // ln 2 * ld a * b
Bei der Kommentierung dieses Codestücks wurde ich vage an ein Ding namens Kommutativgesetz erinnert... Kurz und knapp: ich sehe keinen Grund, weshalb man die Register vertauschen sollte, wenn man danach sowieso beide in einer Multiplikation kombiniert.

II
fmulp st(1), st // ld e * ln 2 * ld a * b = ld a * b = x Nicht gerade das bekannteste Logarithmusgesetz, aber ihr dürft es gern selbst nachrechnen: log_a(b) * log_b(a) = 1, womit die Umformung im Kommentar gültig ist. Da der Code "einfach nur" 2^(ld a * b) berechnet, muss an dieser Stelle natürlich auch ld a * b herauskommen, das wussten also auch die Entwickler des Codes - doch wozu dann überhaupt das ld e und ln 2, wozu so kompliziert?

Fazit:
Nun denke ich natürlich in beiden Punkten zuerst, dass dies einfach zwei Tricks in diesem Machwerk hochperformaten Assemblers sei, doch irgendetwas lässt mich daran zweifeln... ist wohl die Tatsache, dass meine eigengebaute Power-Routine, die diese zwei Punkte verbessert, 2% schneller ist als das Original .

Hier noch der verbesserte Abschnitt:
Delphi-Quellcode:
  fld1 {   fldln2   }
  fxch // a, >1<, b
  fyl2x // >1< * ld a, b
  {   fxch    // b, ln 2 * ld a   }
  fmulp st(1), st // ld a * b = x
  {  fldl2e  // ld e, ln 2 * ld a * b
      fmulp   st(1), st // ld e * ln 2 * ld a * b = ld a * b   }
Mal sehen, ob der Titel ansprechend genug formuliert ist .

[1]Ich beherrsche wahrscheinlich sogar CIL besser als Assembler - allein durch den Reflector .
[2]Hoffe mal, das Ausmaß der zitierten Code-Abschnitte liegt noch im (Copyright-)Rahmen.

Nacht ihr .
Sebastian
Moderator in der EE
  Mit Zitat antworten Zitat