![]() |
Ein bisschen InlineAssembler hilfe :)
Hallo DP,
ich bin grade dabei folgendes:
Delphi-Quellcode:
auf Zeit zu trimmen...
I: integer;
n: double; Zahl: double; while Zahl < i do begin Zahl:= Zahl +1/n; n:= n+1; end; und was geht schneller als etwas ASM? Ist hier jemand willig und in der Lage mir das zu übersetzten, es ist mit hoher dankbarkeit zu rechnen:D Müsste machbar sein, oder? Gruß Spiderpig |
Re: Ein bisschen InlineAssembler hilfe :)
Ich denke mal bei solch trivialen Operationen bringt es keinen Geschwindigkeitsvorteil, wenn man sie in Assembler umsetzt.
|
Re: Ein bisschen InlineAssembler hilfe :)
garkein ganz kleines bisschen?
immerhin wird diese schleife über: 40427833547 mal ausgeführt... |
Re: Ein bisschen InlineAssembler hilfe :)
Hi,
Das ließe sich zwar übersetzen, aber das würde wie schon gesagt eher keinen Geschwindigkeitsvorteil bringen.. eher das Gegenteil ;) |
Re: Ein bisschen InlineAssembler hilfe :)
ok, bin mittlerweile aber trotzdem neugierig geworden wie das aussehen könnte, wenn es jemand weiß, nur zu
dankeschön, Spideprig PS: gibts sonstige Möglichkeiten, dass Programm schneller werden zu lassen (kann man sich mehr Ressourcen klauen?) immerhin sind nur 50% meiner CPU ausgelastet (Quelle Taskmanager)? |
Re: Ein bisschen InlineAssembler hilfe :)
Lass es über die Grafikkarte laufen. (soll schneller gehn :lol: )
Ich weißt zwar nicht wie es geht, aber es würde mich auch mal interessieren. :duck: |
Re: Ein bisschen InlineAssembler hilfe :)
Hmm, du weisst schon, dass du ab irgendwann da präzisionsbedingt nur noch Nullen addierst, oder?
Und auf der GraKa würde DAS hier nix bringen. Zum einen können erst eine Hand voll der neusten überhaupt mit Double-Precision arbeiten, zum anderen sind die nur fix wenn man einProblem sehr gut parallelisieren kann. Das da oben ist mal sowas von sequenziell, da müsste man wenn schon einen parallelisierbaren Ersatz-Algo für aufstellen (falls es den gibt). |
Re: Ein bisschen InlineAssembler hilfe :)
Also, ASM ist nicht gleich ASM.
Für i386 und MMX wirds nicht viel bringen, da der Compiler hier schon gut optimiert. Was steht den zur Verfügung? IA32, SSE, SSE2, SSE3 ??? Multicores ??? 32+||64Bit ??? Wenn für alle CPU's und instructions ASM codiert werden soll, ist das schon ein gewisser Aufwand! Das Ding geht dann schon ab wie eine Rakete. 600% + (Amd64 SSE3) müssten locker drin sein. lg. |
Re: Ein bisschen InlineAssembler hilfe :)
Auch SSE ist nur bei Parallelisierung brauchbar (
![]() |
Re: Ein bisschen InlineAssembler hilfe :)
Naja, vielleicht gehts um einen Bench?
Gewettet wurde ja schon um vieles!! "g" Spiderpig_GER_15 wirds uns hoffentlich verraten! lg. |
Re: Ein bisschen InlineAssembler hilfe :)
Delphi macht etwa das daraus
Delphi-Quellcode:
Eine Optimierung, welche mir einfällt ... kann man das n denn nicht auch als Integer auslegen?
00461AF3 EB1F jmp @@Loop
@@Start: // Unit1.pas.32: Zahl:= Zahl +1/n; 00461AF5 D905381B4600 fld 1 00461AFB DC3424 fdiv &n 00461AFE DC442408 fadd &Zahl 00461B02 DD5C2408 fstp &Zahl 00461B06 9B wait // Unit1.pas.33: n:= n+1; 00461B07 DD0424 fld &n 00461B0A D805381B4600 fadd 1 00461B10 DD1C24 fstp &n 00461B13 9B wait @@Loop: // Unit1.pas.30: while Zahl < i do 00461B18 DB442410 fild &i 00461B1C DC5C2408 fcomp &Zahl 00461B20 9B wait 00461B21 DFE0 fstsw ax 00461B23 9E sahf 00461B24 77CF jnbe @@Start
Delphi-Quellcode:
Hier würde ja alles in einen winzigen Befehl (INC &i) reinpassen und das dan auch noch sofort und ohne warten auf die FPU.
// Unit1.pas.33: n:= n+1;
00461B07 DD0424 fld &n 00461B0A D805381B4600 fadd 1 00461B10 DD1C24 fstp &n 00461B13 9B wait Mit der ASM-Programmierung der Fließkommaeinheit kenn ich mich nicht grade aus, aber vielleicht kann man da ja die Zahl gleich da drinne lassen und nicht immer rein- und wieder rauskopieren. Wenn man das n als Integer hätte, dann würde in der FPU ja nur vorranging mit Zahl gerechnet, wärend n, zusammen mit i womöglich sogar nur in den Registern rumgammeln könnte. PS: wir hatten hier schon ein paar Mal, daß die hochoptimierten (Versuche von Menschen) ASM-Codes wesentlich langsamer oder wenigstens/zumindestens genauso schnell waren, wie die optimierten Delphi-Codes aus 'nem Pascal-Compiler. |
Re: Ein bisschen InlineAssembler hilfe :)
Zitat:
|
Re: Ein bisschen InlineAssembler hilfe :)
Du berechnest anscheinend eine
![]() Mit der richtigen Näherungsformel kannst du die Laufzeit extrem :zwinker: verringern. |
Re: Ein bisschen InlineAssembler hilfe :)
Ungetestet, und keine Ahnung ob das wirklich schneller ist. Spart ein paar OPs, ob es damit auch cycles spart ist nie so sicher :) Zumindest spart es eine Menge pushes auf den FPU Stack! Auch möglich, dass man den Vergleich mit i noch etwas optimieren kann, ohne die Statusbits erst noch nach AX zu schaufeln. Das wait kann man sich erfahrungsgemäß so gut wie immer sparen. In meinen Handoptimierungen hat es bislang zumindest nie weh getan das einfach ersatzlos zu streichen :twisted:
Delphi-Quellcode:
fild &i
fld &n fld &Zahl jmp @@Loop @@Start: // Unit1.pas.32: Zahl:= Zahl +1/n; fld 1 fdiv st(0), st(2) faddp // Unit1.pas.33: n:= n+1; fadd st(1), 1 @@Loop: // Unit1.pas.30: while Zahl < i do fcomp st(2) fstsw ax sahf jnbe @@Start |
Re: Ein bisschen InlineAssembler hilfe :)
Hi, danke für eure bemühungen :D
Kann es sein, dass die 50% Auslastung daher kommen, dass er meinen Dualcore nicht ausnutzt? (hab 2 * 3,0GHZ) wenn ja, wie kann ich das erreichen? Wäre das ein Ansatz für bessere Ergebnisse? N kann leider kein integer sein (es ist zwar ganzzahlig, aber wird größer als der Bereich eines Int). Hat Delphi was genaueres auf Lager als double? Vielleicht sollte ich einen Abbruch machen, wenn 1/n = 0 ist, da es dann ja keinen Sinn mehr macht. Wieviel macht eine zusätzliche If Abfrage in einer solchen Schleife aus? Fatal? Gruß vom Spiderpig, (achso, der Grund ist eigentlich nur persönliches Interesse, und Anfrage meines Bruders ;) ) |
Re: Ein bisschen InlineAssembler hilfe :)
Zitat:
Zitat:
|
Re: Ein bisschen InlineAssembler hilfe :)
extended ist also größer als double? Dann probier ich es mal damit (brauche übrigens nur positive Zahlen).
aber irgendwann ist doch auch aus der letzen 1 eine null geworden?! |
Re: Ein bisschen InlineAssembler hilfe :)
Nur wenn n unendlich wird. Warum verwendest du eigentlich kein Int64?
|
Re: Ein bisschen InlineAssembler hilfe :)
Zitat:
Single = 4 Byte Double = 8 Byte (doppelt so groß wie Single) Extendet = 10 Byte Allerdings ist das mit den "Größen" bei den Fließkommatypen soeine Sache. Es muß nicht unbedingt die Größe größer werden, nur weil der Typ größer wird. Tut es zwar, aber hier kommt es eigentlich mehr auf die nun größere Genauigkeit an. Genaueres siehe OH. Und das mit den 50% (bei einem QuadCore wären es nur ~25% :zwinker:) Bei dir ist halt alles in einem Thread und ein Thread läuft immer nur auf einem Prozessor (gleichzeitig) ... für mehr müßtest du die Beechnung mehrere Threads aufteilen und diese Threads auch noch unterschiedlichen CPUs zuteilen (falls dieses Windows nicht automatisch macht) |
Re: Ein bisschen InlineAssembler hilfe :)
Zitat:
Es hat schon seinen Grund, dass man Gleitkommazahlen nie auf 0 prüfen sollte, schon allein deshalb, weil sich 0 auf verschiedene Weisen darstellen lässt. Nicht umsonst gibt es auch die Funktion IsZero() aus der Unit Math. Bitte schlagt mich nicht, wenn ich jetzt mit meinem Halbwissen irgendwas falsches erzählt habe, aber ich glaube, das ist eifnach eine schlechte Idee. Und dein ursprüngliches Problem kriegst du auf diese Weise garantiert nicht gelöst. |
Re: Ein bisschen InlineAssembler hilfe :)
Bei den 50% liegst du richtig. Weitere Kerne zu nutzen ist aber, wieder ein mal, nur möglich wenn du parallelisieren kannst, wie auch bei der Grafikkarte oder den SIMD Befehlssätzen. Ein einzelner Thread (den dein Programm derzeit wohl nur hat) kann nicht verteilt werden, und eine Aufgabe müsste in Teile zerlegt werden die gleichzeitig verarbeitet werden können. Im Idealfall in so viele Teile wie Cores vorhanden sind, und alle mit ca. gleicher Laufzeit. Dann kannst du Multicores super ausnutzen/lasten, nur ist das hier wie gesagt nicht so möglich. (Wenn man genau wüsste worum es geht, ließe sich evtl. ein zerteilbarer Algo finden, mit genau dem Source wird's jedenfalls nix.)
Genauer als Double (64 Bit) ist nur noch Extended (80 Bit) was native Typen angeht. Dennoch halte ich meine Sinn-Frage aufrecht ;) Wenn du i übrigens als Int64 nimmst hast du die selbe Genauigkeit wie mit Double. Int64 kann maximal 2^64-1 genau darstellen (*) - ich bin mir fast sicher dass Double schon vorher ganzzahlige Sprünge bei großen Werten aufweist. An spätestens der selben Stelle dürfte dann auch die Division 1/N keine verlässlichen Ergebnisse mehr liefern - eher früher. Eine zusätzliche if-Abfrage ist mindestens ein "comp" oder "fcomp+statusbits schaufeln" + einem jump mehr, was auf die geringe Menge Code sicherlich gut messbaren Einfluss haben dürfte. (*) Delphis Int64 ist ja nun leider Vorzeichenbehaftet, so dass nur halb so viele positive Zahlen möglich sind. Ein UInt64 kennt Delphi nicht - zumindest nicht in den Win32 Varianten, bei .NET bin ich unsicher. Auch bin ich nicht auf dem Stand, ob und wie 64er Ints auf der FPU verarbeitet werden können. Alles in allem ist das gesamte Vorhaben problematisch, und solange es keinen größeren Zusammenhang gibt in dem man das ganze evtl. anders angehen könnte, seine Sache auch kaum wert. Edit: 4 Postings dazwischen, uiuiui ich werd alt und lahm :cry: Edit2: Zitat:
|
Re: Ein bisschen InlineAssembler hilfe :)
Double ist 64 Bit groß, insofern muss dort aufgrund des Exponenten die Genauigkeit zwangsläufig unter der von Int64 liegen. Extended stellt 64 Bit für die Mantisse bereit, wenn ich mich nicht irre.
|
Re: Ein bisschen InlineAssembler hilfe :)
UInt64 kennt Delphi schon, auch schon D7,
abe rleider gab es da ein Problem, so daß viele UInt64-Operationen auf die Int64-Proceduren übergeben wurde, statt auf die teilweise schon vorhandenen UInt64-Prozeduren. Seit spätestens Delphi 2006 sollte es da aber keine/kaum noch Probleme mehr geben. PS: was die "genauen" Ganzzahlen in den Fließkommatypen angeht. siehe OH > signifikante Stellen Single = 7-8 Double = 15-16 Extended = 19-20 und 19-20 Stellen haben übrigens auch Int64/UInt64 (nur das Extended nochmal 2 Byte größer ist) |
Re: Ein bisschen InlineAssembler hilfe :)
ein kleiner Vorschlag:
wie oben gesagt, kann man auch mit integerarithmetik arbeiten, dann bietet es sich auch an, die Sprünge zu reduzieren, also zB. sechzehn mal die Operation (für n ... n+15) hinzuschreiben in der Hoffnung, dass sich durch pipelining noch etwas rausholen lässt. Auf die Schnelle konnte ich das jetzt nicht testen :/ Ach und noch etwas: dieses ständige fstp wait ist der blanke Unfug. Ich würde alle Werte im Stack halten, wozu immer speichern und laden? Man muss übrigens nicht ständig prüfen, man kann auch Testen, wann das oft besungene Kind in den Brunnen gefallen ist und dann die letzten Glieder wieder wegsubtrahieren. Eine Vorwärtsberechnung im 16er-Pack sieht dann so aus:
Delphi-Quellcode:
// init
fld [Ergebnisvariable] // 0 fld [const_1] // const = 1 fld [const_1] // n // Schleifenanfang fld st(1) // st(0) = 1 fdiv st(0), st(1) // st(0) = st(0) / n faddp st(3), st(0) // ergebnis = ergebnis + st(0), pop st(0) fadd st(0) st(1) // n = n + 1 fld st(1) // st(0) = 1 fdiv st(0), st(1) // st(0) = st(0) / n faddp st(3), st(0) // ergebnis = ergebnis + st(0), pop st(0) fadd st(0) st(1) // n = n + 1 fld st(1) // st(0) = 1 fdiv st(0), st(1) // st(0) = st(0) / n faddp st(3), st(0) // ergebnis = ergebnis + st(0), pop st(0) fadd st(0) st(1) // n = n + 1 fld st(1) // st(0) = 1 fdiv st(0), st(1) // st(0) = st(0) / n faddp st(3), st(0) // ergebnis = ergebnis + st(0), pop st(0) fadd st(0) st(1) // n = n + 1 // ... noch weitere 12x // Abbruchtest und Schleifenende ... // end FCOMPP // pop st(0) + st(1) fstp [Ergebnisvariable] // save, pop // überschüssige Glieder wieder wegsubtrahieren // ... Man arbeitet da zwar nicht ganz nach den Vorstellungen einer Stackmaschine... aber wer hat gefragt, ob es der FPU gefällt :cheers: Die Idee, viele Befehle untereinander zu schreiben reduziert den Schleifenoverhead ![]() |
Re: Ein bisschen InlineAssembler hilfe :)
Liste der Anhänge anzeigen (Anzahl: 1)
Der Threadersteller war mit dem Zeitbedarf des in #1 gezeigten Pascal-Codes unzufrieden, vermutete, daß ein Assembler-Code schneller sein könnte und bat um Hilfe bei der Umsetzung.
Einige der Kommentare animierten mich, dieses Thema aufzugreifen. Das Resultat findet ihr im Attachment. Zitat:
Zitat:
Natürlich bringt es nicht viel, wenn man sich im Debug-Mode anschaut, was der Compiler aus dem Source-Code gemacht hat, dieses abtippt und dann als die optimierte Assembler-Version ansieht. Zitat:
Zitat:
Das anhängende Programm enthält 9 unterschiedliche Code-Versionen sowie die Möglichkeit diese bezüglich Performance zu testen. Ich glaube, daß die Oberfläche mehr oder weniger selbsterklärend ist, habe aber einen Hilfefile beigefügt, der mit der F1-Taste angezeigt wird. Es wird vorausgesetzt, daß SSE2 verfügbar ist. Die Routinen: 1) "PAS_SC (SC=Single Core) Das ist im Prinzip der vom Threadersteller vorgegebene Code. Diese Version ist nicht multicorefähig. Bei ihr kann nur der Zielwert vorgegeben werden und dieser muß integer sein. 2) "FPU_SC" Das ist die Übersetzung in Assembler. Auch diese Routine ist nicht multicorefähig und auch hier kann nur der Zielwert vorgegeben werden, der allerdings auch eine Fließkommazahl sein darf. 3) "MMX_SC" Ebenfalls eine nicht multicorefähige Assembler-Routine, die aber mit den jeweils unteren Hälften der XMM-Register arbeitet. 4-6) "PAS_A", "FPU_A", "MMX_A" Das sind multicorefähige Versionen. Bei der XMM-Routine wird die volle Breite der XMM-Register genutzt. 7-9) "PAS_B", "FPU_B", "XMM_B" Hier wurde der Vorschlag aus #24 aufgegriffen, nicht nach jeder Rechnung zu prüfen, ob die Abbruchbedingung erfüllt ist sondern z.B. nur alle 16 Mal zu prüfen. Das Ergebnis ist eher ernüchternd, weil so gut wir keine Verbesserung zu verzeichnen ist. Die nachstehende Tabelle zeigt ermittelte Rechenzeiten (CPU Intel Core 2 Duo E8400 3.0 GHz) für die verschiedenen Routinen. Es zeigt sich, daß Verbesserungen der Performance um den Faktor 8 bis 10 erreicht werden. Bei einer Quad-Core CPU dürften die Verbesserung deutlich höher sein.
Code:
Zielwert 20 20 25 25
Cores 1 2 1 2 Zeit in ms ms s s -------- ---- ---- --- --- PAS_SC 4420 - 649 - FPU_SC 2160 - 306 - XMM_SC 2270 - 325 - PAS_A 4160 2130 661 330 FPU_A 2060 1130 298 150 XMM_A 985 547 135 68 PAS_B 4200 2170 612 308 FPU_B 2080 1090 299 151 XMM_B 968 547 136 68 |
Re: Ein bisschen InlineAssembler hilfe :)
Guten Abend Klaus,
vielen Dank für deine Mühe! Mein Vorschlag macht dann Sinn, wenn nur wenige Befehle pro Durchlauf ausgeführt werden müssen und der Übergang zwischen den Befehlen am Ende und am Anfang des Schleifenblocks ohne direkten Bezug auf die gleichen Register oder Speicherinhalte abläuft, zumindest sieht so meine Vorstellung von pipelining-Optimierung aus. Vielleicht kannst du mich ja diesbezüglich erleuchten :-) |
Re: Ein bisschen InlineAssembler hilfe :)
Wie parallelisierst du den Algorithmus? (Hab grad kein Delphi-Parser zur Hand, und das Riesenprogramm war ohne Highlighting bei der Formatierung praktisch undurchschaubar :snowball:)
|
Re: Ein bisschen InlineAssembler hilfe :)
@helgew
Innerhalb der Schleife wird folgende Sequenz 16 Mal ausgeführt. Es ist im Prinzip exakt die von dir vorgeschlagene Sequenz (die sich ja aus dem Problem fast zwingend ergibt) War für mich auch überrraschend, daß das nichts bringt.
Code:
noch mal zum Vergleich die Sequenz aus deinem Vorschlag
fld st(2) // st0=1
fdiv st,st(1) // st0=1/Divisor faddp st(4),st // Summe=Summe+1/Divisor fadd st,st(2) // Divisor=Divisor+1
Code:
fld st(1) // st(0) = 1
fdiv st(0), st(1) // st(0) = st(0) / n faddp st(3), st(0) // ergebnis = ergebnis + st(0), pop st(0) fadd st(0), st(1) // n = n + 1 |
Re: Ein bisschen InlineAssembler hilfe :)
Hi,
Auf 1/n=0 abzuchecken ist mathematisch Quatsch! Da dieser Fall niemals eintreten kann solltest du auf kleine Werte checken. 1/n < epsilon. Wobei epsilon beliebig klein werden kann. es gilt: lim 1/n = 0 n->unendlich Grüsse Rainer |
Re: Ein bisschen InlineAssembler hilfe :)
@Medium:
eigentlich ist das ganz simpel. Aus dem Zielwert (Summe der Reihe) errechne ich die Anzahl der Elemente der Reihe, also die Anzahl der Durchläufe. n = Trunc(Exp(Zielwert-0.57721566490153286)) Nehmen wir an ich will in zwei Threads rechnen, dann muß jeder Threat n div 2 Elemente addieren. also: h:=n div 2 Erster Threat rechnet 1/1 + 1/2 + ... + 1/h Zweiter Thread rechnet 1/(h+1) + 1/(h+2) + ... + 1/n Jeweils nach Ende eines Threads werden die Summen addiert. Wenn alle Threads fertig sind, wird die Summe geprüft und es werden ggfs. noch Elemente addiert bzw. subtrahiert. |
Re: Ein bisschen InlineAssembler hilfe :)
Ah, n ist vorab bestimmbar, alles klar :) Danke!
|
Re: Ein bisschen InlineAssembler hilfe :)
@Medium:
ja, aber es ginge auch ohne das. Nimm an du willst auf 4 Threads verteilen. Thread 1 rechnet 1/1 + 1/5 + 1/9 ... Thread 2 rechnet 1/2 + 1/6 + 1/10 ... Thread 3 rechnet 1/3 + 1/7 + 1/11 ... Thread 4 rechnet 1/4 + 1/8 + 1/12 ... Der erste Thread startet also mit dem Divisor 1 und die folgenden Threads starten mit einem um jeweils 1 höheren Divisor und alle Threads erhöhen den Divisor jeweils um n. Abbruchbedingung aller Thraeds ist Summe>=Zielwert. (Im Nachherein wundert es mich, warum ich das so umständlich gemacht habe.) |
Re: Ein bisschen InlineAssembler hilfe :)
Da hättest du das gleiche Problem, dass ich auch gedanklich hatte, als ich den Algo als nicht parallelisierbar bezeichnet hab: Abbruchbedingung ist die Summe aller Thread-Teilergebnisse, d.h. sie müssten nach jeden Schritt synchronisiert, die Summe gebildet und dann ggf. fortgeführt werden. Zwar "erschlägt" man so viele Divisionen wie man CPUs hat auf ein Mal, jedoch ist der organisatorische Aufwand hoch. Und auch dass alle Threads nach jedem Zyklus angehalten werden müssen, und die Summe ja nur noch ein-threadig berechnet wird ist der Sache auch wenig dienlich.
Und was macht man, wenn n jetzt kein Vielfaches der CPU-Anzahl ist? Dann schießt man ggf. über das Ziel hinaus, und muss das noch extra behandeln. Letztlich müsste man es aber wohl auf einen Versuch ankommen lassen um das brauchbar zu bewerten :). |
Re: Ein bisschen InlineAssembler hilfe :)
@Medium:
Oh, ja, da hast du Recht: Abbruchbedingung wäre für alle Threads die Gesamtsumme. Da bin ich wohl zu kurz gehopst, als ich sage es ginge auch ohne den Wert n zu kennen. Jedoch ist es kein Problem, wenn n kein Vielfaches der CPU-Anzahl ist. Man kann ja (und genau das macht auch mein Programm) nachdem alle Threads fertig sind, noch fehlende Glieder dazu addieren bzw. welche subtrahieren. |
Alle Zeitangaben in WEZ +1. Es ist jetzt 08:21 Uhr. |
Powered by vBulletin® Copyright ©2000 - 2025, Jelsoft Enterprises Ltd.
LinkBacks Enabled by vBSEO © 2011, Crawlability, Inc.
Delphi-PRAXiS (c) 2002 - 2023 by Daniel R. Wolf, 2024-2025 by Thomas Breitkreuz