Der Pentium-Prozessor

Assembler-Optimierung, Parallelisierung

Copyright © 1994  Helmut Schellong

(Mit Angaben der Taktzyklen; auch mit i386, i486)

Zur Hauptseite

Aktuelles Projekt: 59 Funktionen für FPU X87

 

PENTIUM-OPTIMIERTER ASSEMBLER-KODE

Der Pentium-Prozessor ist sehr wahrscheinlich die letzte Entwicklung
aus der Reihe der x86-Prozessoren, der auf direkte Weise kompatibel
zu seinen Vorgängern ist und sicherlich noch für einen längeren
Zeitraum eine nennenswerte Bedeutung haben wird.
Es lohnt sich daher, zumindest Bibliotheks-Funktionen mit optimal
angepaßtem Kode zu entwickeln oder bereits bestehende zu überarbeiten,
weil dadurch sogar eine Vervielfachung der Abarbeitungsgeschwindigkeit
des Kodes erreichbar ist.
Dieser Artikel soll Hilfe und Richtschnur für solche Arbeiten sein.



Übersicht

Die Kerninformationen zum Thema sind kurz und bündig in Tabelle 1
und in den Kapiteln Maßnahmenkatalog und Parallelitätsmechanismus
enthalten.  Lesern mit guten Kenntnissen hinsichtlich Intel-Mnemonics
und Assemblersprache wird hier eine Möglichkeit geboten, sehr schnell
einen hohen Kenntnisstand zu erreichen.
Der Pentium hat eine neue Gleitkomma-Einheit mit gegenüber dem 486er
sehr erheblich gesteigerter Leistungsfähigkeit.
Deshalb wurde Tabelle 2 für Vergleichsbetrachtungen hinzugefügt.
Tabelle 3 enthält Leistungsdaten zu verschiedenen bekannten
Bibliotheks-Funktionen und ermöglicht ebenfalls vergleichende
Betrachtungen.
Listing 1 zeigt den Assembler-Kode einer Funktion aus Tabelle 3.
Erklärungen zu den unverzichtbaren Abkürzungen und Darstellungsarten
im Text und in den Tabellen beenden den Textteil.



Grundlegendes zum Pentium

Der Pentium-Prozessor ist ein 32-Bit-Prozessor mit 64-Bit-Datenbus
und Buszyklus-Pipelining.
Er enthält zwei Integer-Ausführungseinheiten, die U- und die V-Pipeline,
die gemeinsam zwei Instruktionen gleichzeitig abarbeiten können.
Die Adressenberechnung ist ebenfalls doppelt vorhanden. Deshalb bleibt
die Parallelitätsfähigkeit auch mit Operanden für Speicherzugriff (mem)
erhalten. Der »Barrel-Shifter« ist allerdings nur einfach vorhanden
und der U-Pipeline zugeordnet, weshalb Schiebe- und Rotier-Instruktionen
nur eingeschränkt oder überhaupt nicht parallelisierbar sind.
Ein zusätzlicher Prefetch-Puffer hält Sprungziel-Kode bereit, der das
Potential hat, ausgeführt werden zu müssen. Bei tatsächlicher
Verzweigung dorthin wird ohne Verzögerung mit dessen Abarbeitung
begonnen.
Die Gleitkommaeinheit mit sehr erheblich gesteigerter Effizienz ist das
dritte wesentliche Merkmal des Pentium, das diesen leistungsmäßig
deutlich von seinem Vorgänger, dem 486-Prozessor, unterscheidet.
Die 8 Universalregister des Pentium:
  >=386sx         Subregister     Subregister
  32 Bit          16 Bit          8 Bit
  ---------------------------------------
   eax      :      ax      :      ah, al
   ebx      :      bx      :      bh, bl
   ecx      :      cx      :      ch, cl
   edx      :      dx      :      dh, dl
   esi      :      si     source index
   edi      :      di     destination index
   ebp      :      bp     base pointer
   esp      :      sp     stack pointer




Parallelitätsmechanismus

Eine Instruktion, zu der verzweigt wird, die hinter einer call- oder
jccc-Instruktion steht oder die Nachfolgerin einer NP-Instruktion ist,
wird jeweils der U-Pipeline des Pentium zugeordnet,
was man als Grundstellung des Parallelitätsmechanismus bezeichnen kann.
Falls dies eine (E)P-Instruktion ist, kann die V-Pipeline eine
nachfolgende (E)P-Instruktion aufnehmen und gleichzeitig abarbeiten.

Zwei bereits parallelisierte Instruktionen sind als eine einzige
NP-Instruktion anzusehen - mit den daraus resultierenden Konsequenzen
für vor- und nachgeordnete Instruktionen.

Man muß stets genau wissen, welche Instruktionen sich verbinden!
Oberflächliches Vorgehen führt zu Zufallsergebnissen.
Ein Übersehen von Details kann eine längere Kodesequenz »kippen«!



Parallelitätsfähigkeit

Ausgerichtet an der Parallelitätsfähigkeit kann man den Instruktionssatz
in drei Gruppen einteilen:

    P-Instruktionen:                 parallelisierbar
   EP-Instruktionen:   eingeschränkt parallelisierbar
   NP-Instruktionen:           nicht parallelisierbar

Im großen und ganzen kann man sagen, daß diejenigen Instruktionen,
die zum Instruktionssatz des 8086-Prozessors gehören und die man
gleichzeitig den einfachen, elementaren Instruktionen zuordnen kann,
parallelitätsfähig sind.
Es überrascht, daß 'not, neg, xchg' nicht dabei sind.
'not eax' läßt sich durch 'xor eax,0ffffffffH' ersetzen, aber für die
anderen beiden braucht man mindestens 2 Ersatzinstruktionen.

P-Instruktionen sind nahezu ohne Einschränkungen parallelisierbar.
call- und jmp-Instruktionen können sich nur mit davorstehenden
Instruktionen vereinigen.

Bei den EP-Instruktionen gibt es verschiedene Arten der
Eingeschränktheit.
Die Shift- und Rotate-Instruktionen gehen nicht untereinander
und nicht mit einer davorstehenden Instruktion zusammen,
sondern nur mit einer nachfolgenden Instruktion.
Die Kurzform-memoffs-Instruktionen des eax-Registers sind untereinander
nicht parallelitätsfähig!
'fxch' kann sich in einer anderen f-Instruktion »verstecken«.
Bedingte Sprünge (j{ccc}) verzweigen in ½-1½ oder 1-2 Takten.
Ohne Verzweigung genügt ½ oder 1 Takt.
Sie schließen sich grundsätzlich nicht mit Nachfolgerinnen zusammen.

NP-Instruktionen verhalten sich so, als benutzten sie beide
Pipelines des Pentium. Sie sind folglich nicht parallelisierbar.
Ungünstige Positionierung verursacht bis zu zwei Zusatztakte
über den eigenen Zeitbedarf hinaus.
Zum einen kostet es einen Takt, wenn die Position ungeradzahlige
Abschnitte mit P-Instruktionen erzwingt.
Zum anderen entsteht oft ein Zusatztakt, wenn einer NP-Instruktion
mehr als zwei P-Instruktionen vorausgehen.



Taktbedarf und Parallelisierung

Ausgerichtet am Taktbedarf kann man die (E)P-Instruktionen
in 5 Gruppen einteilen:
   1         reine Register-Instruktionen
   1         Lesezugriff auf den Speicher                (mov-memq)
   2         Lesezugriff auf den Speicher                    (memq)
   3         Lese- und Schreibzugriff auf den Speicher       (memqz)
  1-4        Schreibzugriff auf den Speicher             (mov-memz)

Alle diese Instruktionen sind in beliebiger Kombination parallelläufig.
Es ist jedoch nicht so, daß zwei Instruktionen mit beispielsweise
je 3 Takten gemeinsam nur einmal 3 Takte benötigen, sondern ein durch
Parallelisierung erzielter Gewinn beträgt meistens nur 1 Takt!
Einzige Ausnahme ist die einzeln 2 Takte benötigende memq-Instruktion.
Die folgende Darstellung zeigt alle Kombinationen mit dem jeweils
resultierenden Taktbedarf, und in Klammern denjenigen Zusatztakt, den
man sich meistens einhandelt, wenn die beiden parallellaufenden
Instruktionen auf ein und dieselbe 4-Byte-Einheit (DWORD) zugreifen:

   3|3 ==> 5(+1)    3|2 ==> 4(+1)    3|1 ==> 3(+1)
   2|3 ==> 4(+0)    2|2 ==> 2(+1)    2|1 ==> 2(+0)
   1|3 ==> 3(+1)    1|2 ==> 2(+1)    1|1 ==> 1(+1)

Achtung, der soeben beschriebene PZ-Zusatztakt kann zu dem Irrtum
führen, daß Parallelität gar nicht vorliegt - dem ist nicht so!
Auch bei der Folge 'push, pop' wird der P-Taktgewinn durch den
PZ-Zusatztakt wieder eliminiert!



Verhinderung von Parallelläufigkeit

Parallelläufigkeit zweier Instruktionen wird grundsätzlich verhindert
bei beliebiger Verwendung eines Registers in der zweiten Instruktion,
das in der Vorgängerinstruktion als ZIELoperand auftrat.
Diese Abhängigkeit gilt auch bei Subregistern, wenn diese
Bestandteil ein und desselben Registers sind oder wenn eines davon
Bestandteil eines vorher oder nachher benutzten Registers ist!
Beispiel:  Mit 'al, ah, ax' wird jedesmal 'eax' und 'ax' angesprochen!

Gleichzeitiges Vorkommen von Displacement und Immediate schließt
Parallelisierung aus!  (mov-memz, memqz, cmp, test)
Instruktionen, die diese zwei Datenfelder enthalten, muß der Pentium
also exklusiv verarbeiten.
Eine '1' als rechter Operand bei Schiebe- und Rotier-Instruktionen
ist dort ausnahmsweise kein Immediate-Wert, sondern im Opcode enthalten!

'cmp' und 'test' wirken auf nachfolgende Instruktionen so, als wäre
ihr linker Operand Zuweisungsziel!
Das gewinnt Bedeutung in den Fällen, wo man einen bedingten Sprung
nicht direkt folgen lassen möchte, sondern beispielsweise
'mov' oder 'lea', die ja die Bedingungs-Flags nicht verändern.

Die das Register 'esp' implizit verwendenden Instruktionen 
(push,pop,call) können sich nur mit davorstehenden expliziten
esp-Instruktionen vereinigen.

Instruktionen mit hinzugefügtem Präfix gehen untereinander
und mit vorstehenden Instruktionen nicht mehr zusammen.
Das gilt mindestens für die Präfixe '66H' und '67H', die bei Benutzung
von 32-Bit-Registern in 16-Bit-Kodesegmenten -und umgekehrt- den
Opcodes vorangestellt werden.

'nop' ist ein Alias von 'xchg eax,eax'; der Opcode ist in beiden Fällen
'90H'. Der Pentium führt diesen Opcode glücklicherweise als 'nop' aus,
so daß nie irgendwelche Abhängigkeiten zu anderen Instruktionen
auftreten.
'xchg ebx,ebx' wird von Assemblern als Kode-Alignment eingesetzt,
weshalb 'ALIGN' u.a. nur hinter 'jmp' und 'ret' vorkommen sollte.




Unerwünschte Zusatztakte

   BI      Basis/Index-Zusatztakt
   PZ      Zugriff zweier P-Instr. auf ein und dasselbe Datum (-->)
   Z2      Zugriff einer Instr. auf 2 Dateneinheiten gleichzeitig
   PR      Präfix-Takte

Ein Register als Zieloperand, das nachfolgend als Basis- oder
Index-Register zur Adressenberechnung dient, bewirkt einen BI-Zusatztakt
und verhindert gegebenenfalls Parallelisierung.
Beispielsweise können die beiden Instruktionen 'add + lea' zusammen
einen oder zwei oder drei Takte (ohne mem) zur Ausführung benötigen.
Achtung, zwei parallelisierte Instruktionen wirken als eine einzige
zusammengefaßte Instruktion, wodurch die Abhängigkeit, die einen
Zusatztakt verursachen kann, über bis zu zwei zwischenliegende
Instruktionen hinweg wirkt!  Nach erfolgter Parallelisierung sind
diese beiden Instruktionen nämlich quasi verschwunden!
        ; 2 Takte + 1 [eax]-Zusatztakt:
        mov     eax, ecx
        mov     edi, ebp
        dec     edx
        lea     ebx, [eax+4]
Dieser Effekt ist wirklich sehr hartnäckig und tritt ebenfalls auf
bei zwischenliegender Sprung-Instruktion, und bei Verzweigung
(auch durch call!) wird auch diese zum Sprungziel hin überbrückt!
        ; 3.5 Takte + 1 [eax]-Zusatztakt:
        ZIEL:
        nop
        add     ebx, [eax+16]
        nop
        nop
        sub     eax, 4
        jge     SHORT ZIEL
Die tatsächliche Abarbeitungsreihenfolge ist hier maßgebend!

Daten-Alignment ist beim Pentium sehr wichtig:
Jeder »krumme« Adressenwert, der dazu führt, daß EINE Instruktion
auf ZWEI 4-Byte-Einheiten gleichzeitig zugreifen muß (Überlappung!),
hat 3 Cache-Zusatztakte pro Zugriff zur Folge!
Bei memqz-Instruktionen sind dies entsprechend 2*3=6 Zusatztakte!
Ein Ersatz durch Byte-Zugriffe kann schneller sein, obwohl dadurch
die Anzahl der Zugriffe größer ist. Ein Byte kann nicht überlappen!
   Bei Cache-Zusatztakten durch »krumme« Adressen und gleichzeitiger
Parallelläufigkeit werden wiederum bis zu 3 Takte eingespart.
Ein Berechnungsalgorithmus für den letztgenannten Spezialfall existiert,
wird hier aber wegen Nutzenabwägung nicht beschrieben.
   Daten, auf die die Gleitkommaeinheit zugreift, sollten in Begleitung
von  'ALIGN 8'  angelegt werden. Andernfalls können 3 oder 6
zusätzliche Takte die erhebliche Folge sein!

Die Instruktionen 'push,pop,call,ret' verwenden implizit das
Register 'esp'.
Einen von diesen Stack-Instruktionen ausgehenden [esp]-BI-Zusatztakt
gibt es nicht, aber Instruktionen mit 'esp' als explizitem Zieloperand
lösen einen solchen andersherum aus.
Die Ausführung einer ret-Instruktion direkt vor 'call' (call call)
bewirkt 3-3.5 Zusatztakte, wenn es die gleiche Funktion (Adresse) ist.
Es wurden hier bis zu 9 Zusatztakte (3+6=9) gemessen, wobei die
Herkunft der 6 Takte nicht geklärt werden konnte.

Es entsteht ein zusätzlicher Präfix-Takt bei aufeinanderfolgenden
Instruktionen mit vorangestelltem Präfix, was auch für den permanenten
0fH-Präfix von 'mov[sz]x, set, ...' gilt.
Weiterhin entsteht ein Zusatztakt, wenn einer NP-Instruktion mit
permanentem Präfix mehr als zwei P-Instruktionen vorausgehen.
Beispielsweise  'cld,neg,not,xchg'  sind NP-Instruktionen OHNE
permanenten Präfix!

486-Zusatztakte, wie bei Verwendung von Displacement+Immediate, Index,
und Vollregister-Benutzung nach Schreiben in ein zugehöriges
Subregister, gibt es beim Pentium nicht mehr.




mov-Instruktionen mit Memory-Operanden

Schreibende mov-Instruktionen haben einen Taktbedarf, dessen Mittelwert
nicht ganzzahlig ist und von der Anzahl der hintereinander angeordneten
Instruktionen abhängt. Der Bereich geht von 2.2-2.5, über 1.6-1.9,
3.8 (bei n=16), bis hin zu 4 Takten pro Instruktion.
Achtung, dennoch sind sie paarweise parallelisiert!

Beschleunigen kann man, indem in solche Instruktions-Blöcke eingefügte
Nicht-mem-Instruktionen keinen zusätzlichen Zeitbedarf erzeugen, sondern
quasiparallel laufen, was auch für NP-Instruktionen gilt!
Dies geht natürlich nur bis zu einer gewissen Grenze, und zwar muß
mindestens 1 Takt für jede memz übrigbleiben und bei eingefügten NPs
gilt hier ein Wert von etwa 1.4 Takten.
Eine mov-memz gemeinsam mit einer xxx-memqz (3) ergeben 3 Takte.
Folglich gilt mov-memz prinzipiell als 1-Takt-Instruktion: (3+1)-1=3.

Zwei Instruktionen 'mov mem1,r + mov r,mem2' benötigen zusammen
nur 1 Takt!  Solche Kreuz-Anordnungen sind somit sehr schnell
und ergeben ganzzahlige Werte.
Viele solche Doppel hintereinander brauchen jedoch wiederum wesentlich
mehr als jeweils einen Takt, was auch für wiederholtes Anlaufen eines
solchen Doppels in einer -kleinen- Schleife gilt.

Die größte Wirkung im Zusammenhang mit mov-memz wird erzielt, wenn man
einem Block aus bis zu 12 solcher Instruktionen 2 mov-memq nachstellt,
die das erste und das letzte zuvor geschriebene Datenwort lesen.
Diesen Vorgang wickelt der Pentium in nur 6+1=7 Takten ab!
Bis zu 4 KByte im Stück lassen sich so mit ½ Takt pro DWORD schreiben!
(8 KByte: ~0.6 clks,  >=16 KByte: ~6.5/4 clks)
Oberhalb von 8 KByte sollten die nachgestellten mov-memq wieder entfernt
werden, weil dann 4 statt 6.5 Takte erzielt werden.
Voranstellung von memq(z) wirkt auf mindestens 4 mov-memz, ist jedoch
insgesamt »anfälliger«.

Als weiteren »Geheimtip« kann man die an anderer Stelle dargestellte
SWAP-Funktion bezeichnen, die zwei Speicherstellen untereinander
austauscht und dafür (zumeist) nur 2 Takte benötigt.
In modifizierter Form kann die SWAP-Anordnung (Doppelkreuz)
auch andere Aufgaben erfüllen.

Man sollte stets anstreben, daß in Begleitung von mem-Instruktionen
beliebige andere (auch NP) Instruktionen auftreten, weil im weitesten
Sinne davon auszugehen ist, daß der Prozessor Operationen, die ihn daran
hindern sofort zur nächsten mem-Instruktion überzugehen, mit irgend=
welchen Register-Instruktionen (quasi)parallel ausführen kann.

Es darf nicht davon ausgegangen werden, daß die in diesem Kapitel
dargelegten Beobachtungen und Meßwerte für alle anderen Pentium-
-Systeme genau zutreffend sind!




Gleitkomma-Instruktionen

Die FPU-mem-Instruktionen gleichen im Verhalten ziemlich umfassend
ihren Integer-Entsprechungen.
Tabelle 2  zeigt den Taktbedarf der meistverwendeten FPU-Instruktionen.
In diesem Bereich ist der Pentium etwa um den Faktor 3 schneller
als der 486-Prozessor - der Spitzenwert liegt bei 10!
Die häufig gebrauchte Instruktion 'fxch' kann in der V-Pipe parallel
abgearbeitet werden, während die U-Pipe eine andere FPU-Instruktion
bearbeitet.
Ein ganz neuer Aspekt:
Die FPU-Instruktionen 'fmul' und 'fdiv' sind ganz erheblich bzw.
merklich schneller als die Integerversionen 'mul' und 'div'.
   Die 7 Takte für 'fstsw  ax' sind ärgerlich im Zusammenhang
mit 'fcom' == 1 Takt.




C-kompatible Assembler-Funktionen

Tabelle 3 zeigt auf, welches Potential darin enthalten ist, wenn man:
   Library-Funktionen selbst entwickelt,
   auf ein Pentium-System umsteigt, und,
   Kode für den Pentium optimiert.
(Hinweis:  '_i' kennzeichnet Intrinsics, '_L' und '87' Eigenentwcklgn.)
Die Angaben gelten für komplette Funktionsaufrufe einschließlich
der abschließenden Resultatzuweisungen.
Die Funktion fmne_L enthält über 300 FPU-Instruktionen und hatte sich
immer wieder als geeigneter Leistungsmesser bewiesen.

In der Regel kann man davon ausgehen, daß gute Eigenentwicklungen
erheblich schneller sind und einen geringeren Kodeumfang benötigen
als viele mitgelieferte Funktionen.
Desweiteren sollte man sich keineswegs auf Neuauflagen von Funktionen
aus der Standard-Bibliothek beschränken. Selbsterstellte Libraries
sparen ganz erheblich an Entwicklungs- und Kompilierzeit, reduzieren
die Fehlerwahrscheinlichkeit und ergeben höchsteffiziente Programme.
Zur Library-Entwicklung bietet sich auch die Sprache C an!
Durch speziell für den Pentium optimierten Kode wurden zusätzliche
Geschwindigkeitssteigerungen auf das 1.1- bis 4-fache erzielt.




Abkürzungen und Erklärungen

Begriffserklärungen:
   Vollbestückte mov-memz-Instruktion mit Immediate-Wert:
   mov    mem, imm

   mov    Tab_Daten[esi+ebx*8+128], 654321

   Basis-Register:     esi
   Index-Register:     ebx
   Scale-Faktor  :     *8
   Displacement  :     Tab_Daten+128   (4 Byte)
   Immediate-Wert:     654321          (4 Byte)

   Kurzform-eax-memoffs-Instruktionen:
   mov    Tab_Daten, eax
   mov    eax, Tab_Daten

   Assembler-Direktiven:
   BYTE :     1-Byte-Datum
   WORD :     2-Byte-Datum
   DWORD:     4-Byte-Datum
   QWORD:     8-Byte-Datum
   TBYTE:    10-Byte-Datum
   ALIGN:    Gezieltes Hinzufügen von Füll-Bytes zum Zwecke einer
             einheitlichen Ausrichtung (Alignment)

Es werden folgende Abkürzungen benutzt:
   P-, EP- und NP-Instruktionen sind parallelisierbare, eingeschränkt
   parallelisierbare und nicht parallelisierbare Instruktionen.
   xxx  :  Nicht-mov-Instruktionen.
   mem  :  Instruktionen mit Speicherzugriff; Memory-Operand
   memq :  Memory-Operand ist der Quell-Operand.
   memz :  Memory-Operand ist der Ziel-Operand.
   memqz:  Lesen, ändern, zurückschreiben.  (z.B. add mem,r)
   m&d  :  Memory-Instruktionen mit Displacement.
   SWAP :  4 x mov:   r1<--mem1, r2<--mem2, mem1<--r2, mem2<--r1
   m    :  Memory-Operand
   r    :  Register-Operand
   imm  :  Immediate-Wert (Konstante) als Operand

Die Darstellungsformen in den Tabellen folgen den Konventionen,
die in den Intel-Handbüchern vorherrschen:
   shl   r/m,imm    1/3 :
      shl   r,imm    1
      shl   m,imm    3
   shl   r/m,imm    ½¦1/2½¦3 :
      shl   r,imm    ½¦1
      shl   m,imm   2½¦3
      parallelisiert ¦ nicht parallelisiert
   s[ha][lr] :   shl, shr, sal, sar
   rep    movsd       13+2n;4 :
      Initialisierung + (x * ecx) ; Takte ohne rep-Präfix
   { ... } :   Abweichende Werte aus Intel-Handbuch [1]
   Tabelle 2:
      Mittelwert(von-bis)concurrent_processing_Wert  (Spalte i486)
      [ optional ]
      a ¦(oder) b

Die in diesem Artikel gemachten Angaben zum Pentium-Prozessor
stützen sich fast ausschließlich auf eigene Messungen.
Hauptsächlich bei Abweichungen wurden entsprechende Werte aus einem
Intel-Handbuch [1] in geschweiften Klammern hinzugefügt.




Maßnahmenkatalog

Nachfolgend eine Aufzählung von optimierenden Maßnahmen in Kurzform -
- das, was den Pentium noch schneller macht:
  • Einsatz von NP-Instruktionen möglichst weitgehend vermeiden.
    Besonders diejenigen mit 0fH-Präfix (zusätzlich ab dem 386er).
  • Verwendung von P- und EP-Instruktionen forcieren.
  • Möglichst große, zusammenhängende Gruppen aus P- und EP-Instruktionen
    bilden. Dabei idealerweise eine geradzahlige Anzahl anstreben.
  • NP-Instruktionen nicht inmitten von P- und EP-Instruktionen anordnen,
    sondern vorher oder nachher; in Schleifen möglichst zuoberst.
  • EP-Instruktionen (shift,rotate) nicht nach P- und nicht vor
    NP-Instruktionen setzen, sondern vor P-Instruktionen.
  • Aufeinanderfolgende EP-Instruktionen (shift,rotate) vermeiden.
  • Register (Quelle, Ziel, Basis oder Index), die zuvor Zieloperand
    waren, verhindern Parallelisierung mit der Vorgängerin.
    Achtung, beispielsweise 'al' und 'ah' sind auch voneinander abhängig,
    weil sie beide Bestandteil des Registers '(e)ax' sind!
    NP- und bereits parallelisierte Instruktionen sind natürlich
    in diesen Hinsichten isoliert.
  • Adreßregister (Basis oder Index), die zuvor Zieloperand waren,
    bewirken einen Zusatztakt und verhindern ggf. Parallelisierung.
    Achtung, zwei parallelisierte Instruktionen wirken als eine einzige
    zusammengefaßte (NP-)Instruktion, wodurch verlangsamende Wirkungen
    bis zu zwei Instruktionen überspringen können!
  • Man beachte die impliziten Verwendungen des Stackpointers 'esp'!
  • Daten-Alignment durch 'ALIGN 4(8)' ist sehr wichtig.
    Zugriffe mittels »krummer« Adressen, die auf zwei DWORDs (QWORDs)
    gleichzeitig erfolgen (Überlappung), sollten möglichst nicht
    vorkommen! Nichtbeachtung kann den Taktbedarf von bis zu 12
    Instruktionen verschwenden!
  • Zugriff zweier parallelisierter mem-Instruktionen
    auf ein und dasselbe Datum vermeiden. (Zusatztakt!)
  • Gleichzeitige Verwendung von Displacement und Immediate-Operanden
    vermeiden.
  • xxx-memq-Instruktionen möglichst untereinander parallelisieren.
  • Besonders memz-Instruktionen mit beliebigen anderen vermischen.
  • Bei mem-Instruktionen memz- und memq(z)-Instruktionen kombinieren!
    Eine Folge 'mov-memz, mov-memq' ist mehr als doppelt so schnell
    wie eine einzelne mov-memz-Instruktion!
  • Füll-Instruktionen (Kode-Alignment) an anderen Stellen als hinter
    'jmp' und 'ret' unbedingt vermeiden.
  • Memory-Operand (zumindest) bei 'xchg' (NP) tunlichst unterlassen.
    Diese Instruktion hat dann einen abenteuerlich hohen Taktbedarf!
  • Mit Ausnahme bei den zwangsweise zugeordneten Instruktionen gibt
    es längst keinen Grund mehr für eine »Verliebtheit« in das
    eax-Register! Das heißt, es sollten keine Extras vorgenommen werden,
    nur um dieses Register verwenden zu können.
    Manche Instruktionen mit 'eax' sind zwar kürzer - aber langsamer!
  • Instruktionen mit vorangestelltem Präfix vermeiden.
  • Wirklich »echter« 32-Bit-Kode in 32-Bit-Segmenten hat beim Pentium
    eine noch weiter gesteigerte positive Wirkung.
    32-Bit-Instruktionen in 16-Bit-Segmenten (z.B. DOS) sind nämlich
    präfix-behaftet und desweiteren gibt es viele Segment-Präfixe und
    viele zusätzliche langsame Segmentregister-Instruktionen!



^

Tabelle 1a

========================================================================
P-Instruktionen      Taktbedarf      EP-Instruktionen     Taktbedarf
------------------------------------------------------------------------
add                  ½¦1             s[ha][lr]  r/m,imm   ½¦1/2½¦3
adc                  ½¦1             s[ha][lr]  r/mem,1   ½¦1/2½¦3
sub                  ½¦1             s[ha][lr]  r/m&d,1   ½¦1/2½¦3
sbb                  ½¦1             r[oc][lr]  r/mem,1   ½¦1/2½¦3
inc                  ½¦1             r[oc][lr]  r/m&d,1   ½¦1/2½¦3
dec                  ½¦1             call                 ½¦1
and                  ½¦1             jmp                  ½¦1
or                   ½¦1             j{ccc}           ½-2,½¦1
xor                  ½¦1             fxch                 0¦1
lea                  ½¦1             mov  moffs,eax       ½-4 {½¦1}
mov                  ½¦1             mov  eax,moffs       ½¦1
cmp                  ½¦1
test                 ½¦1
nop                  ½¦1
push                 ½¦1
pop                  ½¦1
jmp                  ½¦1
call                 ½¦1
mov  r,mem           ½¦1
mov  mem,r/imm       ½-4 {½¦1}
xxx  mem,r          2½¦3
xxx  r,mem        1¦1½¦2
SWAP mem<->mem      2¦>2
========================================================================


^

Tabelle 1b

========================================================================
NP-Instruktionen      Taktbedarf     NP-Instruktionen      Taktbedarf
------------------------------------------------------------------------
neg       r/m         1/3            xchg     (lock!?)     2¦3/11.5
not       r/m         1/3            xchg r,m + mov r,m    24.5 !!!
s[ha][lr] m&d,imm     3              xchg r,m + 2x mov r,m 36.5 !!!
s[ha][lr] r/m,cl      4/5            xxx    m&d,imm        3
ro[lr]    r/m,imm     1/3            mov    m&d,imm        1-4
ro[lr]    r/m,cl      4/5            push   mem            2
rc[lr]    r/m,imm     8/10           pop    mem            3
rc[lr]    r/m,cl      7/9            rep    movsd       13+2n;4
shld                  5¦4            rep    movsd      {13+1n;4}
mov[zs]x              3/3(+1)        rep    lodsd        7+3n;2
set{ccc}              1  (+1)        rep    stosd    9+(2-4)n;3-4.5
cld                   2              rep    stosd      { 9+1n;3}
enter                 11             rep__  cmpsd        9+4n;5
leave                 3              rep__  scasd        8+4n;4
ret                   2
pushad                5
popad                 5
========================================================================



^

Tabelle 2

========================================================================
FPU-Instruktionen:     Pentium          i486             i387/386
-----------------------------------------------------------------------
fld    m32,64,80,st    1,1,3,1          3,3,6,4          20,25,44,14  
fst[p] mem32,64        3¦4-8   {2/2}    7,8              44,45        
fstp   mem80           4¦5-10  {3/3}    6                53           
fst[p] st(i)           1                3                11¦12        
fild                   1-3     {3/1}    12-17()2-8       45-72        
fist[p]                6,8,8+  {6/6}    33               79-97        
fld{1¦z}               2                4                24,20        
fld{const}           <=8       {5/3}    8()2             40¦41        
fxch                   0¦1              4                18           
fadd[p]                3                10(8-20)7        23-37        
fsub[p][r]             3                10(8-20)7        30(24-36)    
fiadd                  4-6     {7/4}    23(19-35)7       57-85        
fisub[r]               4-6     {7/4}    23(19-35)7       57-86        
fmul                   3                11,14()8,11      27-57,52     
fmulp                  3                16()13           29-57        
fimul                  4-6     {7/4}    24()8            61-87        
fdiv[p][r]             19¦33¦39         73()70           90-94,120-141
fidiv                  22¦36¦42         73()70           90-94,120-141
fsqrt                  70               86()70           122-129      
fabs                   1                3                22           
fchs                   1                6                25           
fprem                  32    {16-64}    84(70-138)2      74-155       
frndint                16    {9-20}     29(21-30)7       66-80        
fscale                 32    {20-31}    31()2            67-86        
fxtract                12    {13/13}    19(16-20)4       70-76        
f[u]com[p][p]          1 !   {4/1}      4,5              24-26,-31    
ficom[p]               4 !   {8/4}      17()1            56-75        
ftst                   1 !   {4/1}      4()1             28           
fxam                   18    {21/21}    8                30-38        
fsin, fcos             70    {16-126}   241(193-279)2    122-772/+76  
fsincos                93    {17-137}   291(243-329)2    194-809/+76  
fptan                  124   {17-173}   244(200-273)70   191-497/+76  
fpatan                 130   {19-134}   289(218-303)5    314-487      
f2xm1                  50    {13-57}    242(140-279)2    211-476      
fyl2x[p1]              95    {22-111}   311(196-329)13   120-538      
fstsw   ax             7 !   {6/2}      3(+3)            15,13(+6)    
fstcw                  4     {2/2}      3(+3)            15(+6)       
fldcw                  8     {7/7}      4                19           
ffree                  2     {1/1}      3                18           
fwait                  1                1-3              6            
-----------------------------------------------------------------------



^

Tabelle 3

========================================================================
Funktion                     Zeit [æs]       Takte    Prozessor-f[MHz]
------------------------------------------------------------------------
i= atoi    ("1234567899");      25.50         638       i386DX-25
i= atoi    ("1234567899");       4.50         270      Pentium-60
i= atoi_L  ("1234567899");       1.48          89      Pentium-60
i= atoi    (     "12345");       2.58         155      Pentium-60
i= atoi_L  (     "12345");       1.04          62      Pentium-60
i= atoi    (         "5");       1.07          64      Pentium-60
i= atoi_L  (         "5");       0.37          22      Pentium-60
s= itoa    ( 1234567899 );      15.02         901      Pentium-60
s= itoa_L  ( 1234567899 );       6.41         384      Pentium-60
s= itoa87  ( 1234567899 );       3.23         194      Pentium-60
s= itoa    (      12345 );       8.71         522      Pentium-60
s= itoa_L  (      12345 );       2.62         157      Pentium-60
s= itoa87  (      12345 );       3.66         219      Pentium-60
s= itoa    (          5 );       3.77         226      Pentium-60
s= itoa_L  (          5 );       0.76          45      Pentium-60
s= itoa87  (          5 );       3.73         224      Pentium-60
i= strcmp_i( abcd ,  abcD );     1.072 !       64      Pentium-60
i= strcmp_i( abcd , "abcD");     0.602 !       36      Pentium-60
i= strcmp  ("abcd", "abcD");     0.770         46      Pentium-60
i= strcmp_L("abcd", "abcD");     0.351         21      Pentium-60

   fmne_L  (/* SPEZfp */);     592.00       14800       i387DX-25
   fmne_L  (/* SPEZfp */);     285.00        7125      cx387DX-25
   fmne_L  (/* SPEZfp */);      41.20        2472      Pentium-60
d= pow     (b, e);             578.67       14467       i387DX-25
d= pow87   (b, e);              77.50        1938       i387DX-25
d= pow     (b, e);             326.67        8167      cx387DX-25
d= pow87   (b, e);              34.00         850      cx387DX-25
d= pow     (b, e);              27.20        1632      Pentium-60
d= pow87   (b, e);               4.40         264      Pentium-60
d= powi87  (b,     10);         44.33        1108      i386+387-25
d= powi87  (b,     10);         22.67         567      i386+cx387-25
d= powi87  (b,     10);          1.43          86      Pentium-60
d= powi87  (b,   1000);          3.82         229      Pentium-60
d= powi87  (b, 100000);          5.73         344      Pentium-60

s= findstr_L(s, a1, a2);         260 ms   pro  MByte   Pentium-60
s= findstr_L(s, a1, a2);  OPT.    27 ms!  pro  MByte   Pentium-60
------------------------------------------------------------------------



^

Listing 1

;       sc/11.07.94
        TITLE   atoi_L
        .386
        .MODEL small
PUBLIC  _atoi_L
        .CODE   ; 32-Bit-Segment
_atoi_L PROC                            ;Takte
        push    ebx                     ; 1
        push    esi                     ; 1
        mov     esi, [esp+12]           ; 2, 4+2*4=12
        xor     eax, eax                ; 2
        push    edx                     ; 3, wegen [esi]-BI
        push    ecx                     ; 3, wegen [esi]-BI
        mov     dl, [esi]               ; 4
        xor     ebx, ebx                ; 4
        cmp     dl, '0'                 ; 5
        jae     SHORT $digit            ; 5
        inc     esi
$digit:
        push    edi                     ; 6
        xor     edi, edi                ; 6
        mov     ecx, 10                 ; 7
$loop:
        mov     al, [esi]               ; 1/7
        add     edi, edi                ; 1, edi(=ebx) *= 2
        sub     al, '0'                 ; 2
        js      SHORT $break            ; 2
        cmp     al, 9                   ; 3
        ja      SHORT $break            ; 3
        lea     ebx, [edi+ebx*8]        ; 4, ebx *= 10
        inc     esi                     ; 4
        add     ebx, eax                ; 5
        dec     ecx                     ; 5
        mov     edi, ebx                ; 6
        jg      SHORT $loop             ; 6
$break:
        cmp     dl, '-'                 ; 1
        jne     SHORT $pos              ; 1
        neg     ebx
$pos:
        mov     eax, ebx                ; 2
        pop     edi                     ; 2
        pop     ecx                     ; 3
        pop     edx                     ; 3
        pop     esi                     ; 4
        pop     ebx                     ; 4
        ret                             ; 6
        ALIGN 4         
_atoi_L ENDP
        END
[1]     Pentium Family User's Manual,
        Volume 3:  Architecture and Programming Manual, INTEL 1994
[2]     i486 Microprocessor, Programmer's Reference Manual, INTEL 1990
[3]     387DX User's Manual, Programmer's Reference, INTEL 1989

Zur Hauptseite