Elementare Optimierung
Wenn es um Optimierung geht, sind viele ablehnend: „Wenn Computer jetzt so schnell sind, welchen Sinn hat es dann, ein paar Prozent schneller zu sein?“ Dies ist durchaus sinnvoll. Mit Ausnahme der Entwicklung spezifischer Software wie Grafiken, Bilder und Multimedia ist eine bewusste Optimierung in den meisten Fällen nicht erforderlich, wenn Entwickler jedoch Code schreiben Zeit, Sie hatten bereits das Bewusstsein für die Optimierung, Sie können die Entwicklungseffizienz sicherstellen oder sogar verbessern.
Natürlich ist das Design des Algorithmus der Kern der Optimierung. In den meisten Fällen wird die Ausführungseffizienz des Programms hauptsächlich durch das Gesamtverständnis des Entwicklers für das Programm, das Design des Algorithmus usw. bestimmt! Aber manchmal macht auch die Optimierung von Details Sinn!
Darüber hinaus erfordert diese Art der Optimierung in vielen Fällen nicht das direkte Schreiben von Code durch Assembly. In diesem Fall kann sie jedoch auch die Überlegenheit der Beherrschung von Assembly-Kenntnissen widerspiegeln!
Wie zum Beispiel die folgenden zwei Funktionen:
Funktion GetBit(i: Cardinal; n: Cardinal): Boolean;
beginnen
Ergebnis := Boolean((i shr n) and 1);
Ende;
Funktion GetBit(i: Cardinal; n: Cardinal): Boolean;
beginnen
Ergebnis := Boolean((1 shl n) and i);
Ende;
Entsprechender Assembler-Code:
MOV ECX, EDX
SHR EAX, CL
UND EAX, 01 $
MOV ECX, EDX
MOV EDX, 01 $
SHL EDX, CL
UND EAX, EDX
Sie haben die gleiche Funktion, sie nehmen alle den Wert eines bestimmten Bits von i an, geben True zurück, wenn es 1 ist, und False, wenn es 0 ist!
Oberflächlich betrachtet denken Sie vielleicht, dass die Ausführungseffizienz der beiden Funktionen gleich ist, aber tatsächlich gibt es einen Unterschied. Die Schiebeoperation des ersten Programms wird gemäß der Standardaufrufkonvention in Delphi ausgeführt. Zu diesem Zeitpunkt wird der Wert im EAX-Register gespeichert und der Schiebevorgang kann direkt abgeschlossen werden. Um den Schiebevorgang für den unmittelbaren Wert 1 abzuschließen, muss er zuerst in das Register übertragen werden. Es muss also noch eine Anweisung geben! Natürlich sind nicht in allen Fällen notwendigerweise weniger Befehle schneller als mehr Befehle. Bei der spezifischen Ausführung müssen wir auch Aspekte wie den Taktzyklus der Befehlsausführung und die Paarung von Befehlen berücksichtigen (mehr dazu später). Das Problem kann nur dann unabhängig gelöst werden, wenn Vergleiche nur in bestimmten Codeumgebungen durchgeführt werden können.
Unter normalen Umständen ist dieser Unterschied in der Effizienz zu vernachlässigbar, aber es ist nie schlecht, beim Programmieren auf Optimierung zu achten! Wenn sich ein solcher Code in der innersten Schicht einer Schleife befindet und sich über eine große Anzahl von Schleifen N Taktzyklen ansammeln, kann der Unterschied in der Ausführungseffizienz sehr groß werden!
Das Obige ist nur ein kleines Beispiel. Es ist ersichtlich, dass Sie effizienteren, detaillierten Code in Hochsprachen schreiben und gleichzeitig die Entwicklungseffizienz sicherstellen können, wenn Sie während der Entwicklung über einige Probleme nachdenken. Es kommt jedoch immer noch vor, dass eine detaillierte Optimierung mithilfe eingebetteten Assemblercodes durchgeführt werden muss, und manchmal kann das Schreiben von Code durch die Anwendung eingebetteten Assemblercodes auch effizienter werden.
Wenn Sie die Bytereihenfolge einer 32-stelligen Zahl umkehren müssen, wie können Sie dies in Delphi vollständig in der Hochsprache tun? Sie können die Verschiebung verwenden, Sie können auch die integrierte Funktion Swap mehrmals aufrufen, aber wenn Sie an eine BSWAP-Anweisung denken, wird alles sehr einfach.
Funktion SwapLong(Wert: Kardinal): Kardinal;
asm
BSWAP EAX
Ende;
Hinweis: Wie oben wird der Wert von Value im Register EAX gespeichert und der 32-stellige Wert wird auch über EAX zurückgegeben, sodass nur ein Satz erforderlich ist.
Natürlich sind die meisten eingebetteten Assembly-Optimierungen nicht so einfach, aber es ist schwierig, mit den wenigen im College erworbenen Assembler-Kenntnissen eine tiefergehende Optimierung zu erreichen. Erfahrung kann nur durch kontinuierliche Sammlung und Vergleich kompilierter Assembler-Codes gewonnen werden! Glücklicherweise ist die detaillierte Optimierung in den meisten Fällen nicht der Hauptbestandteil des Programmdesigns.
Wenn das entwickelte Programm jedoch Grafiken, Bilder, Multimedia usw. umfasst, ist dennoch eine tiefergehende Optimierung erforderlich! Glücklicherweise kann Delphi6 gute Unterstützung bieten, sei es die Optimierung von Gleitkommaanweisungen oder die Anwendung von MMX, SSE, 3DNow usw. Selbst wenn Sie möchten, dass frühere Versionen von Delphi diese erweiterten CPU-Befehlssätze unterstützen oder in Zukunft neue CPU-Befehlssätze unterstützen möchten, können Sie die vier von Delphi unterstützten Assembleranweisungen DB, DW, DD und DQ in der eingebetteten Assembly verwenden ( Im offiziellen Delphi6-Sprachhandbuch von Borland heißt es nur, dass DB, DW und DD unterstützt werden. Die numerische Darstellung eingefügter verwandter Anweisungen kann ebenfalls flexibel implementiert werden.
wie:
DW $A20F //CPUID
DW $770F //EMMS
DB $0F, $6F, $C1 //MOVQ MM0, MM1
Das Verstehen von Anweisungen ist nur die Grundlage. Wenn Sie den Algorithmus nach dem Entwerfen von FPU, MMX und SSE weiter optimieren möchten, müssen Sie auch einige technische Eigenschaften der CPU selbst verstehen.
Schauen wir uns die folgenden zwei Codeteile an:
asm
ADD [a], ECX
ADD [b], EDX
Ende
asm
MOV EAX, [a]
MOV EBX, [b]
EAX, ECX HINZUFÜGEN
EBX, EDX HINZUFÜGEN
MOV [a], EAX
MOV [b], EBX
Ende
Ist der zweite effizienter? Falsch, wie oben erwähnt, bedeuten weniger Anweisungen keine hohe Ausführungseffizienz. Den relevanten Informationen zufolge beträgt der Taktzyklus für die Ausführung der beiden Anweisungen im ersten Abschnitt des Codes 3 (jede Anweisung muss drei Leseschritte abschließen). Ändern und Schreiben Schritt), die von den sechs Anweisungen im zweiten Abschnitt des Codes ausgeführten Taktzyklen sind alle 1. Die beiden Codeteile sind also gleich effizient? Wieder einmal falsch, tatsächlich wird der zweite Codeabschnitt effizienter ausgeführt als der erste Codeabschnitt! Warum? Da CPUs nach der Pentium-Klasse über zwei Pipelines zum Ausführen von Anweisungen verfügen, können zwei benachbarte Anweisungen gleichzeitig ausgeführt werden, wenn zwei benachbarte Anweisungen gepaart werden können! Was sind die spezifischen Gründe für die beiden oben genannten Codeteile?
Obwohl die beiden Anweisungen im ersten Code gepaart werden können, beträgt der erforderliche Gesamtausführungstaktzyklus 5 statt 3, während die sechs Anweisungen im zweiten Code parallel ausgeführt werden können, was zu diesem Ergebnis führt.
Apropos, das sind alles sehr einfache Beispiele, die Ihnen allein nicht viel weiterhelfen können. Wenn Sie ein bestimmtes Programm wirklich optimieren möchten, sollten Sie nach speziellen Artikeln zur FPU- und MMX-Optimierung suchen oder technische Handbücher finden, um Technologien wie „Out-of-Order-Ausführung“ und „Branch-Vorhersage“ zu studieren und zu studieren. Ich hoffe nur, dass sich meine Freunde im College nicht nur auf diese „geldverdienenden“ Entwicklungstools und modischen neuen Technologien konzentrieren, sondern mehr Zeit damit verbringen können, den Grundstein zu legen. Nur mit einer soliden Grundlage können Sie sich schnell neues Wissen aneignen Dann können Sie neue Entwicklungstools und -fähigkeiten schneller beherrschen ... (tausend Wörter weglassen).
Andererseits sollte Wissen immer noch zur Lösung praktischer Probleme genutzt werden. Wenn Sie sich jeden Tag nur auf technische Details konzentrieren, werden Sie vielleicht ein ausgezeichneter Hacker, aber Sie werden nie erstklassige Software entwickeln. Daher muss der grundlegende Zweck weiterhin darin bestehen, Werte zu schaffen. Also... ich werde nicht mehr darüber reden, es wird nicht wirklich wie ein technischer Artikel aussehen, wenn ich noch mehr darüber rede. ^_^
Anhang: Bei der Programmoptimierung müssen nicht nur die Ausführungseffizienz berücksichtigt werden, sondern auch Größenprobleme (kleine Größen können den Speicher schneller laden und die Befehlsdekodierung und andere Aufgaben schneller abschließen). Verwenden Sie beispielsweise SUB EAX, EAX oder XOR EAX , EAX statt MOV EAX, $0 Obwohl ihre Ausführungstaktzyklen beide 1 sind, ist die Befehlslänge des ersteren (2 Bytes) offensichtlich kürzer als die des letzteren (5 Bytes). Da es sich bei den oben genannten Angaben jedoch ausschließlich um Details handelt, wurde die Frage der Lautstärke nicht erwähnt. Weitere Probleme bei der Größenreduzierung sollten dem Compiler überlassen werden, wenn Sie den eingebetteten ASM-Code schreiben.