Code alignment

Für Fragen zur Programmiersprache auf welcher Lazarus aufbaut
Antworten
ruewa
Beiträge: 153
Registriert: Sa 12. Apr 2014, 14:43

Code alignment

Beitrag von ruewa »

Hallo in die Runde!

Beim Versuch, die Laufzeit einer Funktion zu optimieren, bin ich auf einen bemerkenswerten Effekt gestoßen.

Zunächst zur Umgebung: AMD64, Debian Wheezy, Laz 1.2.4, FPC 2.6.4, SVN 45510, x86-64linux-gtk2. Der Effekt tritt auf bei Optimization Level 2 und 3 (NoDebug). Die Funktionen befinden sich in einer inc-Datei. Es geht dabei um enge Routinen (50-200 ns - im konkreten Fall um eine Case-Insensitive ShortString-Variante von System.Pos), die in großen Loops getestet werden, die Messwerte sind valide und reproduzierbar (> 5 Sekunden, keine Micromessungen).

Angenommen, ich untersuche das Laufzeitverhalten von Func3, dann ergibt diese Anordnung:

Code: Alles auswählen

function Func1: TSomeType;
begin
   ...
end;
 
function Func2: TSomeType;
begin
   ...
end;
 
function Func3: TSomeType;
begin
   ...     // Laufzeit / Durchgang: 136 ns
end;


deutlich andere Laufzeiten als diese Anordnung:

Code: Alles auswählen

function Func1: TSomeType;
begin
   ...
end;
 
function Func3: TSomeType;
begin
   ...     // Laufzeit / Durchgang: 150 ns
end;
 
function Func2: TSomeType;
begin
   ...
end;


Der Unterschied liegt in einer Größenordnung von 10 %. Ähnliches geschieht, wenn man lediglich eine vorstehende Routine leicht modifiziert:

Code: Alles auswählen

function Func2: TSomeType;
begin
   ...
  asm           //
    nop         // zusätzlich eingefügt
  end;          //
end;
 
function Func3: TSomeType;
begin
   ...          // Zeitmessung
end;

Auch hier führt das bloße Einfügen einer Dummy-Anweisung in Funktion 2 dazu, daß sich die Laufzeit von Funktion 3 um 10 % verschlechtert (oder auch verbessert).

Die Ursache dafür liegt auf der Hand: Durch das Einfügen der nop-Anweisung wächst der Code von Func1 um ein Byte und die Startposition von Func3 verschiebt sich um dieses eine Byte - einzig und allein das Code Alignment ändert sich. Dies fängt die Level-2-Optimierung offenbar nicht auf, was umso erstaunlicher ist, als diese Optimierung innerhalb der Routinen durchaus Code Alignment vornimmt (durch Einfügen von nop (90h), xchg %ax, %ax (6690h), data32 xchg %ax, %ax (666690h) etc. - Btw: Weiß jemand, welcher asm-Anweisung das letztere Disassemblat enspricht?). Das hatte ich bisher nicht auf dem Radar, was umso frustrierender ist, als ich mich schon geraume Zeit mit dem Thema Laufzeitoptimierung herumschlage...

Nun gibt es die Möglichkeit, durch die Compilerdirektive {$CODEALIGN...} Einfluß darauf zu nehmen, siehe http://www.freepascal.org/docs-html/prog/progsu9.html#x16-150001.2.9. Mein Problem ist allerdings, daß es mir bisher weder bei der Recherche noch bei meinen Versuchen gelungen ist, ein konsistentes Bild zu gewinnen, welche Einstellungen sinnvoll sind und welche nicht. Alle vermeintlichen Erkenntnisse werden beim Versuch, sie zu schärfen, irgendwann wieder durchkreuzt und das alles scheint manchmal wie gewürfelt...

Es macht erstmal keinen Sinn, meine ganze Testumgebung jetzt hier einzustellen, evtl. könnte ich ein einfaches Testprogramm schreiben, das diesen Effekt demonstriert. Aber erstmal die Frage: Hat sich mit diesem Thema schon mal jemand herumgeschlagen? Ich nehme an, vernünftige Code-Alignment-Einstellungen sind hochgradig CPU-abhängig: Hat jemand Erkenntnisse, was da sinnvoll ist und was nicht?

Gruß Rüdiger

Socke
Lazarusforum e. V.
Beiträge: 3158
Registriert: Di 22. Jul 2008, 19:27
OS, Lazarus, FPC: Lazarus: SVN; FPC: svn; Win 10/Linux/Raspbian/openSUSE
CPU-Target: 32bit x86 armhf
Wohnort: Köln
Kontaktdaten:

Re: Code alignment

Beitrag von Socke »

Ich habe gelesen, dass der Open64-Compiler wesentlich (30 %) effizienteren Binärcode für die AMD-Bulldozer-Architektur erzeugen kann als z.B. der GCC. Seit dem bezweilfe ich, dass einige Optimierungen überhaupt sinnvoll sind bzw. eigentlich für jede Prozessorarchitektur individuell ausgetestet werden müssen. Sobald man damit jedoch fertig ist, wird die getestete Prozessorarchitektur nicht mehr hergestellt.
Die Konsequenz wäre, für jede Architektur eigene Binärdateien auszuliefern. Auf selbstübersetzten Linuxen wäre das noch einigermaßen sinnvoll, aber danach schränkt sich der Nutzen im allgemeinen doch eher ein.
ruewa hat geschrieben:Ich nehme an, vernünftige Code-Alignment-Einstellungen sind hochgradig CPU-abhängig: Hat jemand Erkenntnisse, was da sinnvoll ist und was nicht?

Nach allem was ich weiß, gibt es CPUs, die nur auf ausgerichtete Speicher-Blöcke zugreifen können und entsprechend bei nicht ausgerichteten Blöcken herumschieben müssen. Es gibt im Bugtracker auch ein Ticket in dem das zufällige Einfügen von 10 % nops diskutiert wird, da dies auf einigen Prozessoren Leistungssteigerungen mit sich bringt.
MfG Socke
Ein Gedicht braucht keinen Reim//Ich pack’ hier trotzdem einen rein

mschnell
Beiträge: 3444
Registriert: Mo 11. Sep 2006, 10:24
OS, Lazarus, FPC: svn (Window32, Linux x64, Linux ARM (QNAP) (cross+nativ)
CPU-Target: X32 / X64 / ARMv5
Wohnort: Krefeld

Re: Code alignment

Beitrag von mschnell »

Nur ein paar Anmerkungen, ohne Anspruch auf Exaktheit.

Der Prozessor greift auf den Cache (oder z.B. der Layer 1 Cache auf den Layer 2 Cache) vermutlich in einer ziemlich großen Wortbreite zu, die natürlich vom jeweiligen Prozessor-Typ abhängt. Ich schätze z.B. 256 Bit = 64 Byte. Da macht es sicher etwas aus, wie viele solche Worte eine Funktion belegt. Bei einer sehr kleinen Funktion also eines oder zwei. Schlechtes Alignment würde die Anzahl dieser Zugriffe also verdoppeln. Aber wer macht "vorsorglich" schon ein align auf 64 Byte ?

NOPs einfügen würde nur dann ein reproduzierbares Ergebnis bringen, wenn man sich sein kann, dass der gesamte Programm-Code überhaupt in immer derselben Alignment geladen wird. Kann man das ?

Ausschlaggebend ist das Hardware-Alignment. Wird die MMU immer so parametriert, dass das physikalische und das virtuelle Alignment im Rahmen der Datenpfad-Breite der CPU identisch ist ?

-Michael

ruewa
Beiträge: 153
Registriert: Sa 12. Apr 2014, 14:43

Re: Code alignment

Beitrag von ruewa »

Hab noch etwas tiefer gegraben und die Kompilate verschiedener Varianten verglichen.

Zunächst: Das "asm nop end;" am Ende der Funktion ist offenbar ein schlechtes Beispiel. Tatsächlich übersetzt der Compiler die Funktion vollkommen anders (und umständlicher), sobald ein asm-Block innerhalb des Quelltextes auftaucht. Und zwar von der ersten Zeile an, nicht erst ab der Position des Blocks. Das gilt auch dann, wenn man die veränderten Register dem Compiler anzeigt, also z.B. so:

Code: Alles auswählen

function Func2
Begin
  ...
  asm
    nop
  end['rcx'];
end;
 

Man kann daraus aber nicht den Schluß ziehen, daß dieser asm-Block quasi vorsorglich alle Optimierung komplett ausschalten würde, denn auch dann enthält das Kompilat eingesprengte Alignments.

Anstelle des asm-Blocks habe ich es dann einmal mit einem zusätzlichen "inc(i)" versucht, am Ende der Funktion, wobei das i eine lokale und später nicht mehr gebrauchte Integer-Variable ist. Auch damit läßt sich der Laufzeitunterschied nachfolgender Prozeduren reproduzieren.

Was also ändert sich durch das Vertauschen der Reihenfolge zweier Prozeduren im Quelltext oder das geringfügige Ändern irgendeiner vorhergehenden Prozudur? Es ist nicht so, daß der Compiler überhaupt kein Alignment der Prozedur-Startpositionen vornehmen würde (die Startposition also um lediglich 1 Byte verschoben wäre). Das voreingestellte Alignment basiert vielmehr auf 8, die nachfolgende Prozedur läuft (in meinem Fall!) schneller, wenn sie auf 0 beginnt, und 10 % langsamer, wenn sie auf einer 8 startet. Der Effekt ist verschwunden, sobald ich die Compileranweisung "{$CODEALIGN PROC=16}" hinzufüge.

Das läßt sich aber keineswegs generalisieren! Denn der Startpunkt der Funktion ist meistens ziemlich unwichtig, er wird pro Durchgang nur einmal angesprungen. Befinden sich innerhalb der Funktion Loops, also etwa beim Durchsuchen eines Strings nach einem Treffer mit einem Vergleichsstring (z.B. bei System.Pos) - und nur solche Prozeduren lohnt es, zu optimieren - werden die Startpositionen der Loops zum entscheidenden Merkmal. Das funktioniert im meinem Fall also nur zufällig dann besser, wenn die Funktion auf der 0 beginnt, es könnte genausogut umgekehrt sein.

Deshalb habe ich es auch einmal mit

Code: Alles auswählen

{$CODEALIGN LOOP=16}

versucht. Der Effekt war durchwachsen berauschend! Meine Funktion ("ShortPosSensitive", oben als "Func3" bezeichnet), an deren Optimierung ich arbeite und die ohnehin schon ziemlich gut war (25 % schneller als ihr RTL-Pendant), hat sich damit schlagartig um nochmal 12 % verbessert. Die Vergleichsfunktionen allerdings (u.a. eine 1:1-Kopie der ShortString-Variante von System.Pos) konnten davon nicht profitieren und blieben gleich schnell. Ein Aufstocken auf {$CODEALIGN LOOP=32} hat übrigens dann keine Veränderungen mehr bewirkt.

mschnell hat geschrieben:Der Prozessor greift auf den Cache (oder z.B. der Layer 1 Cache auf den Layer 2 Cache) vermutlich in einer ziemlich großen Wortbreite zu, die natürlich vom jeweiligen Prozessor-Typ abhängt. Ich schätze z.B. 256 Bit = 64 Byte. Da macht es sicher etwas aus, wie viele solche Worte eine Funktion belegt. Bei einer sehr kleinen Funktion also eines oder zwei. Schlechtes Alignment würde die Anzahl dieser Zugriffe also verdoppeln. Aber wer macht "vorsorglich" schon ein align auf 64 Byte ?

NOPs einfügen würde nur dann ein reproduzierbares Ergebnis bringen, wenn man sich sein kann, dass der gesamte Programm-Code überhaupt in immer derselben Alignment geladen wird. Kann man das ?

Da bin ich mir nicht sicher, ob das wirklich ein entscheidender Punkt ist. Soweit ich es mir aus den verschiedenen Quellen zusammengereimt habe, arbeitet der AMD64 (nur mit dem habe ich mich eingehender beschäftigt) etwa folgendermaßen: Die als nächstes abzuarbeitenden Instruktionen werden in den Prozessor-Cache geladen und danach bewertet, welche Schritte von vorhergehenden (noch abzuarbeitenden) Entscheidungen abhängig sind und welche nicht. Die unabhängigen Aktionen (z.B. das Laden einer neuen Variablen in ein Register) werden dann in einer Art Hintergrundprozeß parallel abgearbeitet und stehen dann schon zur Verfügung, wenn ihr Ergebnis gebraucht wird - das macht die Sache superschnell (der Fachausdruck dafür ist "Superskalarität"). Verzweigungen infolge von Entscheidungen (z.B. ob die Zählvariable eines Loops ihre Abbruchgröße erreicht hat) stellen dann insofern Stolpersteine dar, als der Prozessor vorab nicht wissen kann, wie die Entscheidung ausfällt und welchem Pfad er demnächst zu folgen hat. Auf sagenhaft schlaue Weise stellt er deshalb "branch prediction"-Vermutungen an, welche Entscheidung wohl wahrscheinlicher sein dürfte und arbeitet sich an diesem Pfad schon mal vorsorglich ab. Fällt die Entscheidung nun anders aus, war die Vorarbeit für die Katz' und der Prozessor muß erstmal sein Zeug aufräumen und umorganisieren - das kostet dann wiederum Laufzeit. Das ist wohl (zumindest teilweise) der Grund, weshalb man - in Pascal - sagt "Wähle If-then-else-Anweisungen so, daß der wahrscheinlichere Pfad im If-Zweig steht". Ich bin mir aber nicht sicher, ob das so stimmt! Vom Prozessor her gesehen, ist es eher umgekehrt, da lautet die Faustregel "Bedingte Sprünge nach unten sollten zum unwahrscheinlicheren Zweig führen, solche nach oben zum wahrscheinlicheren". Das ist dann allerdings erst der Anfang, innerhalb von Loops lernt der Prozessor hinzu und korrigiert seine Voraussagen. Hochkompliziert, faszinierend und saumäßig schwer zu durchdringen das alles...

Um auf das Alignment zurückzukommen: Soweit ich es verstehe, sind die 16/32/64 oder was auch immer Byte sozusagen die Blockgrenzen für das Bestücken des Prozessor-Caches. Innerhalb dieses Cache-Blocks kann der Prozessor aber nur eine Verzweigungsstelle im Auge behalten (siehe hier und hier). Wenn ich das richtig verstehe, könnten also vermeintlich so elegante Konstrukte wie

Code: Alles auswählen

asm
  cmpw   %dx, %ax  // Vergleich zweier Word-Registerinhalte
  ja     .L1       // Wenn AX > DX, dann springe zu L1
  je     .L2       // Wenn AX = DX, dann springe zu L2
  ...              // Wenn AX < DX, dann weiter im Text
end;

den Prozessor unverhofft ausbremsen, während ein paar nop's zwischen den Sprungentscheidungen L1 und L2 Wunder wirken könnten.

Socke hat geschrieben:Ich habe gelesen, dass der Open64-Compiler wesentlich (30 %) effizienteren Binärcode für die AMD-Bulldozer-Architektur erzeugen kann als z.B. der GCC. Seit dem bezweilfe ich, dass einige Optimierungen überhaupt sinnvoll sind bzw. eigentlich für jede Prozessorarchitektur individuell ausgetestet werden müssen. Sobald man damit jedoch fertig ist, wird die getestete Prozessorarchitektur nicht mehr hergestellt.

Das hat der Teufel gesehen!

Man kann aber auch nicht sagen, daß die Optimierungen wenig wirksam wären. Mir ist da was ziemlich Verrücktes passiert: Ich hatte mich vor Monaten damit beschäftigt, (ShortString-) Compare-Funktionen (die von Sortieralgorithmen exzessiv genutzt werden) zu optimieren (und die Ergebnisse auch hier eingestellt, seither konnte ich sie nochmals um etwa 10% bzw. 20 % verbessern). Da war es so, daß die Assembler-Varianten phänomenal schnell waren und meine Pascal-Pendants keine Chance hatten, diese zu toppen. Was mich damals nicht verwundert hat, außerdem hat das auch ziemlich Spaß gemacht. Als ich mich jetzt an die (case-sensitiven und -insensitiven) ShortString-Pos-Funktionen gesetzt habe, ist es mir ziemlich mühelos gelungen, eine Pascal-Alternative zu System.Pos zu schreiben, die 25 % (bzw. seit heute früh ca. 36 %...) schneller war als ihr RTL-Gegenstück. Dann habe ich es für X86-64 in Assembler versucht und erwartet, nochmal 10-20 % rausholen zu können. Pustekuchen! Es gelingt mir partout nicht, näher als 10 % (jetzt etwa 20 %) an die vom FPC-Compiler optimierte Pascal-Variante heranzukommen. Einen Teil davon kann ich mir erklären (Übergabe von Parametern in den Registern statt auf dem Stack), aber das kann nur ein kleiner Teil sein. Die Assembler-Version sieht perfekt aus, schlank, trickreich, sparsam, elegant, fehlerfrei - aber sie fetzt nicht. Wahrscheinlich sind es genau solche Konstrukte wie oben, die den Flaschenhals ausmachen. Jedenfalls habe ich die FPC-Optimierung auf diese Weise trotz manchen Stirnrunzelns schätzen gelernt!

Socke hat geschrieben:Es gibt im Bugtracker auch ein Ticket in dem das zufällige Einfügen von 10 % nops diskutiert wird, da dies auf einigen Prozessoren Leistungssteigerungen mit sich bringt.

Ja, das habe ich auch gelesen. Wobei ich mich an dem "zufällig" störe. Mag sein, daß schon zufällig gestreute nop's was bringen, aber es muß schon mehr System dahinterstecken. Soweit ich das verfolgen konnte, herrscht da immer noch gewaltiges Rätselraten. Es ist aber auch ein verrücktes Zeugs... Und ich bin mir keineswegs sicher, das Ganze auch nur annähernd korrekt verstanden zu haben.

Nochmal kurz zu den No-Operations: Hier bzw. hier auf Seite 94 werden zeitsparende Multi-Byte-NOP's beschrieben, mit denen man sich das Alignment nachfolgender Anweisungen von Hand zurechtschieben kann. Ich habe aber noch nicht herausgefunden, wie ich diese Macros/Opcodes in FreePascal umsetzen kann, die Dokumentationen (sowohl FPC-seitig als auch die des GNU Assemblers) geben da nichts her. Ist es möglich, in asm-Blöcke auch reine Opcodes einzufügen (das wäre das Einfachste)? Vielleicht kann mir da jemand weiterhelfen?

Gruß Rüdiger

FPK
Beiträge: 65
Registriert: Mi 21. Mai 2008, 19:38
Wohnort: Erlangen

Re: Code alignment

Beitrag von FPK »

ruewa hat geschrieben:Was also ändert sich durch das Vertauschen der Reihenfolge zweier Prozeduren im Quelltext oder das geringfügige Ändern irgendeiner vorhergehenden Prozudur? Es ist nicht so, daß der Compiler überhaupt kein Alignment der Prozedur-Startpositionen vornehmen würde (die Startposition also um lediglich 1 Byte verschoben wäre). Das voreingestellte Alignment basiert vielmehr auf 8, die nachfolgende Prozedur läuft (in meinem Fall!) schneller, wenn sie auf 0 beginnt, und 10 % langsamer, wenn sie auf einer 8 startet. Der Effekt ist verschwunden, sobald ich die Compileranweisung "{$CODEALIGN PROC=16}" hinzufüge.

Code: Alles auswählen

{$CODEALIGN LOOP=16}



Für "Hotspots" (häufig genutzten Code) ist im Allgemeinen ein Alignment von 16 auf i386 und x86-64 in der Tat meist sinnvoll. FPC nutzt jedoch standardmässig nur 4 oder 8 Byte Aligments als Kompromiss, da dies den Binärcode nicht so stark vergrößert, d.h. schnellere Ladezeiten und es ist in den Caches Platz für mehr Code.

Nochmal kurz zu den No-Operations: Hier bzw. hier auf Seite 94 werden zeitsparende Multi-Byte-NOP's beschrieben, mit denen man sich das Alignment nachfolgender Anweisungen von Hand zurechtschieben kann. Ich habe aber noch nicht herausgefunden, wie ich diese Macros/Opcodes in FreePascal umsetzen kann,


FPC macht das automatisch in Code-Abschnitt, vgl. compiler/x86/aasmcpu.pas, tai_align.calculatefillbuf

ruewa
Beiträge: 153
Registriert: Sa 12. Apr 2014, 14:43

Re: Code alignment

Beitrag von ruewa »

FPK hat geschrieben:FPC macht das automatisch in Code-Abschnitt, vgl. compiler/x86/aasmcpu.pas, tai_align.calculatefillbuf


Danke für den Hinweis, Florian!

Meine Frage war leider etwas unpräzise, entschuldige! Natürlich wäre es völlig unsinnig, innerhalb von Pascal-Quelltext mit NOP's um sich zu schmeißen, man würde nur dem Compiler ins Handwerk pfuschen. Das macht lediglich innerhalb von asm-Blöcken Sinn, die der Compiler 1:1 übernimmt. Da wiederum kann, soweit ich sehe, ein Alignment äußerst sinnvoll sein.

Was funktioniert, sind ein- und zwei-Byte-NOP's, die kann man als Instruktion ("nop" bzw. "xchg %ax, %ax") codieren. Wenn der Compiler ein 3-Byte-NOP (666690h) einfügt, wird das im Assembler-Fenster als "data32 xchg %ax, %ax" decodiert. So kann man es aber nicht in einen asm-Block einfügen, das akzeptiert der Compiler nicht, und mit Längen-Suffixen herumzuprobieren, hat auch nichts gebracht. Der FPC-Inline-Assembler verarbeitet nur eine Teilmenge der gas-Anweisungen, z.B. werden die 2- und 4-Byte-Varianten "nopw" und "nopl" ignoriert und als ein-Byte-nop übersetzt. Viele gas-Direktiven wie z.B. ".fill" akzeptiert der FPC-Compiler auch nicht. Die Frage ist also: Gibt es einen Weg, innerhalb eines (natürlich {$ifdef x86_64}-) asm-Blocks z.B. das in den AMD-Handbüchern empfohlene "NOP7_OVERRIDE_NOP: 0f1f8000000000h" unterzubringen (als Instruktion, Macro, Opcode oder was auch immer)?

Ein bißchen bin ich allerdings schon weitergekommen. Anders, als im FPC-Programmer's Guide dargestellt, kann der FPC-Inline-Assembler inzwischen mit der ".balign"-Direktive umgehen. Eine Anweisung wie

Code: Alles auswählen

asm
  ...
  .balign 16
  .LLoopStart: ...
end;

führt dazu, daß der Compiler das Sprungziel "LLoopStart" auf die nächste 16-Byte-Grenze verschiebt und den Zwischenraum mit ein- bis vier-Byte breiten NOP's (90h - 66666690h) auffüllt. Ist noch nicht ganz das, was ich mir vorstelle, aber so geht's schon mal.

Gruß Rüdiger

ruewa
Beiträge: 153
Registriert: Sa 12. Apr 2014, 14:43

Re: Code alignment

Beitrag von ruewa »

Hallo!

Wie oben schon erwähnt, hatte ich eine alternative Funktion zur Shortstring-Variante von System.Pos geschrieben, die deutlich schneller ist als ihr RTL-Pendant. Dabei stellte sich unerwartet heraus, daß die Compiler-Anweisung

Code: Alles auswählen

{$CODEALIGN LOOP=16}

diese Funktion um nochmals 12 % beschleunigt, während alle anderen Vergleichsfunktionen davon nicht profitieren.

Mich hat nun interessiert, an welchen Stellen der Compiler No-Operation-Anweisungen eingefügt hat, um welche Sprungziele an den Anfang von 16-Byte-Blöcken zu plazieren. Daher habe ich die beiden Versionen mit und ohne Loop Alignment gegeneinandergestellt und verglichen:

DevPosSensitive_CodeAlign.pdf
(86.07 KiB) 64-mal heruntergeladen

Dabei fallen drei Dinge auf.

Erstens befinden sich alle zusätzlich eingefügten Alignments an den Startpositionen interner Loops (was keine große Erkenntnis ist, so war's zu erwarten).

Zweitens führt der Compiler per Voreinstellung auch ohne die Codealign-Loop-Anweisung an diesen Stellen ein Alignment durch, nur eben auf der Basis 4. Der Versuch, die Funktion mit einem Loop-Alignment von 8 zu kompilieren, hat eine zwar erkennbare, aber nur geringfügige Verbesserung um 2,5 % erbracht (120 zu 123 ns, gegenüber 108-109 ns bei Loop-Align 16).

Drittens, und das finde ich am interessantesten, befinden sich ausnahmslos alle Alignments hinter unbedingten Sprüngen. D.h., sie werden nie selbst durchlaufen, die ganzen NOP's kosten also keinen einzigen Takt Laufzeit. Das ist nicht selbstverständlich, bei meinen eigenen Assembler-Versionen hatte ich versucht, Sprünge soweit wie möglich zu reduzieren und die am häufigsten zu durchlaufenden Codeblöcke in einen gewissermaßen ungestörten, "natürlichen" Ablauf zu plazieren - das ist möglicherweise ineffizient.

Interessant wird nun sein, herauszufinden, ob zusätzliche Alignments bzw. NOP's innerhalb von Codeblöcken, also z.B. zwischen aufeinanderfolgenden bedingten Sprüngen, Laufzeitvorteile bringen können oder nicht.

Gruß Rüdiger

Edit: Noch eine Anmerkung zu der Funktion DevPosSensitive: Die ist aus ziemlich frühen Versuchen entstanden, daß sie deutlich besser läuft als das kompaktere System.Pos, hat mich eher überrascht. Sie muß noch nicht das Optimum sein, auch habe ich sie noch nicht auf einem i386-Rechner getestet. Das Einzige, was sich bisher sagen läßt, ist, daß sie mit dem dokumentierten Sample von Substrings auf einem AMD 64 nur 2/3 der Laufzeit von System.Pos benötigt und mit diesen Testdaten auf Fehlerfreiheit getestet ist. Insbesondere wird noch näher zu analysieren sein, wie sich die Laufzeitunterschiede auf kurze und lange Substrings aufteilen. Es ist also noch keine "veröffentlichungsreife" Version. Dieser Stand hat sich nur zufällig angeboten, das Thema Alignment mal etwas genauer zu untersuchen.

FPK
Beiträge: 65
Registriert: Mi 21. Mai 2008, 19:38
Wohnort: Erlangen

Re: Code alignment

Beitrag von FPK »

ruewa hat geschrieben:Ein bißchen bin ich allerdings schon weitergekommen. Anders, als im FPC-Programmer's Guide dargestellt, kann der FPC-Inline-Assembler inzwischen mit der ".balign"-Direktive umgehen. Eine Anweisung wie

Code: Alles auswählen

asm
  ...
  .balign 16
  .LLoopStart: ...
end;

führt dazu, daß der Compiler das Sprungziel "LLoopStart" auf die nächste 16-Byte-Grenze verschiebt und den Zwischenraum mit ein- bis vier-Byte breiten NOP's (90h - 66666690h) auffüllt. Ist noch nicht ganz das, was ich mir vorstelle, aber so geht's schon mal.


Was stellst Du Dir genau vor?

ruewa
Beiträge: 153
Registriert: Sa 12. Apr 2014, 14:43

Re: Code alignment

Beitrag von ruewa »

FPK hat geschrieben:Was stellst Du Dir genau vor?

Die Frage ist, ob bzw. wie es möglich ist, 3- und mehr Byte lange NOPs im Inline-Assembler zu codieren.
1- und 2-Byte-NOPs sind klar:

Code: Alles auswählen

nop               { Opcode 90h }
xchg %ax, %ax     { Opcode 6690h }

3- und 4-Byte breite NOPs codiert FPC, im Assemblerfenster werden sie dargestellt als:

Code: Alles auswählen

Opcode 666690h       data32 xchg %ax, %ax
Opcode 66666690h     data32 data32 xchg %ax, %ax

Aber wie wird das codiert? Die GAS-Anweisungen nopw und nopl ignoriert der Inline-Assembler.
 
Die .balign-Direktive führt zum Auffüllen der Lücken mit diesen 1-4-Byte breiten NOPs. Darüberhinaus gibt es aber noch längere NOPs, AMD z.B. empfiehlt diese:
 

Code: Alles auswählen

0f1f440000                      NOP5_OVERRIDE_NOP
660f1f440000                    NOP6_OVERRIDE_NOP
0f1f8000000000                  NOP7_OVERRIDE_NOP
0f1f840000000000                NOP8_OVERRIDE_NOP
660f1f840000000000              NOP9_OVERRIDE_NOP
66660f1f840000000000            NOP10_OVERRIDE_NOP
6666660f1f840000000000          NOP11_OVERRIDE_NOP

Siehe auch http://stackoverflow.com/questions/25545470/long-multi-byte-nops-commonly-understood-macros-or-other-notation

In vielen Fällen ist das ohne Belang, wenn eine Sprungstelle isoliert dasteht und die vorstehenden NOPs niemals durchlaufen werden. Dann ist .balign das Mittel der Wahl. Aber manchmal könnten längere NOPs (vielleicht?) nützlich sein.

Gruß Rüdiger

FPK
Beiträge: 65
Registriert: Mi 21. Mai 2008, 19:38
Wohnort: Erlangen

Re: Code alignment

Beitrag von FPK »

D. h. ein .balign, welches längere Sequenzen nutzt, würde Dir reichen?

Ansonsten kannst Du beliebige Sequenzen mit .byte aa,bb,cc einbetten, die natürlich dann eine feste Länge haben.

BeniBela
Beiträge: 309
Registriert: Sa 21. Mär 2009, 17:31
OS, Lazarus, FPC: Linux (Lazarus SVN, FPC 2.4)
CPU-Target: 64 Bit

Re: Code alignment

Beitrag von BeniBela »

ruewa hat geschrieben:
Opcode 66666690h data32 data32 xchg %ax, %ax



Das NOP des Teufels!

ruewa
Beiträge: 153
Registriert: Sa 12. Apr 2014, 14:43

Re: Code alignment

Beitrag von ruewa »

FPK hat geschrieben:D. h. ein .balign, welches längere Sequenzen nutzt, würde Dir reichen?


Nice to have, vielleicht könnte es auch im Kontext der Compileroptimierung nützlich sein?

Aber eigentlich hat mir jetzt schon dieser Hinweis auf die Sprünge geholfen (ich hatte diese Direktive beim Wühlen in den Handbüchern schlicht mißverstanden):

FPK hat geschrieben:Ansonsten kannst Du beliebige Sequenzen mit .byte aa,bb,cc einbetten, die natürlich dann eine feste Länge haben.


Herzlichen Dank!

Gruß Rüdiger

FPK
Beiträge: 65
Registriert: Mi 21. Mai 2008, 19:38
Wohnort: Erlangen

Re: Code alignment

Beitrag von FPK »

ruewa hat geschrieben:
FPK hat geschrieben:D. h. ein .balign, welches längere Sequenzen nutzt, würde Dir reichen?


Nice to have,


http://svn.freepascal.org/cgi-bin/viewv ... sion=29772

vielleicht könnte es auch im Kontext der Compileroptimierung nützlich sein?


Naja, dem Compiler ist das egal, der erzeugt auch erstmal nur ein balign. Den Rest must der Assembler machen, entweder intern oder extern.

PS: Danke für den Hinweis :)

Antworten