Programmoptimierung

Im Informatik, Programmoptimierung, Codeoptimierung, oder Softwareoptimierung, ist der Prozess der Änderung eines Softwaresystems, um einen Aspekt davon zu ermöglichen effizient oder weniger Ressourcen verwenden.[1] Im Allgemeinen a Computer Programm kann so optimiert werden, dass es schneller ausgeführt wird, oder um mit weniger operieren zu lassen Speicher oder andere Ressourcen oder weniger Strom zeichnen.

Allgemein

Obwohl das Wort "Optimierung" die gleiche Wurzel wie "optimal" aufweist, ist es für den Optimierungsvorgang selten, ein wirklich optimales System zu erzeugen. Ein System kann im Allgemeinen nicht absolut optimal gemacht werden, sondern nur in Bezug auf eine bestimmte Qualitätsmetrik, die im Gegensatz zu anderen möglichen Metriken steht. Infolgedessen ist das optimierte System in der Regel nur in einer Anwendung oder für ein Publikum optimal. Man kann die Zeit verkürzen, die ein Programm zur Ausführung einer Aufgabe zum Preis für den Verbrauch mehr Speicher macht. In einer Anwendung, in der der Speicherplatz eine Prämie ist, kann man absichtlich einen langsameren auswählen Algorithmus um weniger Speicher zu verwenden. Oft gibt es kein "Einheitsgröße passt zu allen" Designs, das in allen Fällen gut funktioniert, also Ingenieure machen Kompromisse Die Attribute von größtem Interesse optimieren. Darüber hinaus ist der Aufwand, der erforderlich ist, um ein Stück Software vollständig optimal zu machen - nicht in der Lage, weitere Verbesserungen zu erzielen - fast immer mehr als angemessen für die Vorteile, die aufgenommen würden. Der Optimierungsvorgang kann also gestoppt werden, bevor eine vollständig optimale Lösung erreicht wurde. Glücklicherweise ist es oft so, dass die größten Verbesserungen früh im Prozess vorkommen.

Selbst für eine bestimmte Qualitätsmetrik (z. B. Ausführungsgeschwindigkeit) verbessern die meisten Optimierungsmethoden das Ergebnis nur; Sie haben keinen Vorwand, eine optimale Ausgabe zu erzeugen. Superoptimierung Ist der Prozess, um wirklich optimale Ausgabe zu finden.

Optimierungsstufen

Die Optimierung kann bei einer Reihe von Ebenen auftreten. In der Regel haben die höheren Niveaus einen größeren Einfluss und sind später in einem Projekt schwerer zu ändern. Dies erfordern erhebliche Änderungen oder ein vollständiges Umschreiben, wenn sie geändert werden müssen. Daher kann die Optimierung typischerweise durch Verfeinerung von höher zu niedriger verlaufen, wobei die anfänglichen Gewinne größer sind und mit weniger Arbeit erzielt werden und spätere Gewinne kleiner sind und mehr Arbeit erfordern. In einigen Fällen hängt die Gesamtleistung jedoch von der Leistung sehr niedriger Teile eines Programms ab, und kleine Änderungen in einem späten Stadium oder eine frühzeitige Berücksichtigung von Details auf niedrigem Niveau können übergroße Auswirkungen haben. In der Regel wird der Effizienz während eines Projekts eine gewisse Überlegung erhoben - obwohl dies erheblich unterschiedlich ist -, aber eine größere Optimierung wird häufig als Verfeinerung angesehen, die zu spät, wenn überhaupt, zu verfolgen ist. Bei länger laufenden Projekten gibt es in der Regel Optimierungszyklen, in denen die Verbesserung eines Bereichs Einschränkungen in einem anderen aufweist und diese normalerweise eingeschränkt werden, wenn die Leistung akzeptabel ist oder Gewinne zu klein oder kostspielig werden.

Da die Leistung Teil der Spezifikation eines Programms ist-ein Programm, das ungewöhnlich langsam ist, ist nicht für den Zweck geeignet: Ein Videospiel mit 60 Hz (Frames-per-Sekunde) ist akzeptabel, aber 6 Frames-per-Sekunden ist unannehmbar abgehackt- Die Leistung ist von Anfang an eine Überlegung, um sicherzustellen, dass das System eine ausreichende Leistung liefern kann, und frühe Prototypen müssen eine in etwa akzeptable Leistung haben, damit das endgültige System (mit Optimierung) eine akzeptable Leistung erzielt. Dies wird manchmal in der Überzeugung weggelassen, dass eine Optimierung immer später durchgeführt werden kann, was zu Prototypsystemen führt, die viel zu langsam sind - oft von einem Größenordnung oder mehr - und Systeme, die letztendlich Misserfolge sind, weil sie ihre Leistungsziele architektonisch nicht erreichen können, wie die Intel 432 (1981); oder solche, die jahrelange Arbeit benötigen, um akzeptable Leistung zu erzielen, wie Java (1995), die nur akzeptable Leistung mit erzielten Hotspot (1999). Der Grad, in dem sich die Leistungsveränderung zwischen Prototyp und Produktionssystem ändert und wie es für die Optimierung zugänglich ist, kann eine signifikante Quelle für Unsicherheit und Risiko sein.

Designstufe

Auf höchstem Niveau kann das Design optimiert werden, um die verfügbaren Ressourcen, die Ziele, Einschränkungen und die erwartete Verwendung/Last am besten zu nutzen. Das architektonische Design eines Systems beeinflusst seine Leistung überwiegend. Zum Beispiel würde ein System, das eine Netzwerklatenzbindung ist (wo die Netzwerklatenz die Hauptbeschränkung für die Gesamtleistung ist), optimiert, um Netzwerkfahrten zu minimieren und eine einzige Anforderung (oder keine Anforderungen, wie in einem Push -Protokoll) und nicht mehrere Roundtrips. Die Auswahl des Designs hängt von den Zielen ab: Bei der Gestaltung a Compiler, wenn eine schnelle Zusammenstellung die wichtigste Priorität ist, a Ein-Pass-Compiler ist schneller als a Multi-Pass-Compiler (Angenommen unter der Annahme derselben Arbeit), aber wenn die Geschwindigkeit des Ausgabescode das Ziel ist, erfüllt ein langsamerer Multi-Pass-Compiler das Ziel besser, obwohl es selbst länger dauert. Die Auswahl der Plattform- und Programmiersprache tritt auf dieser Ebene auf und erfordert häufig ein vollständiges Umschreiben System, Auswahl der Architektur (Kundenserver, Peer-To-Peerusw.) tritt auf Konstruktionsebene auf und kann schwierig zu ändern sein, insbesondere wenn nicht alle Komponenten synchronisiert werden können (z. B. alte Clients).

Algorithmen und Datenstrukturen

Angesichts eines Gesamtdesigns eine gute Wahl von effiziente Algorithmen und Datenstrukturenund eine effiziente Implementierung dieser Algorithmen und Datenstrukturen kommt als nächstes. Nach dem Design die Wahl von Algorithmen und Datenstrukturen beeinflussen die Effizienz mehr als jeder andere Aspekt des Programms. Im Allgemeinen sind Datenstrukturen schwieriger zu ändern als Algorithmen, da eine Datenstrukturannahme und ihre Leistungsannahmen im gesamten Programm verwendet werden, obwohl dies durch die Verwendung von minimiert werden kann Abstrakte Datentypen In Funktionsdefinitionen und beibehalten der konkreten Datenstrukturdefinitionen, die auf einige Orte beschränkt sind.

Für Algorithmen besteht dies hauptsächlich darin, sicherzustellen, dass Algorithmen konstant O (1), logarithmisch O (log n), linear o (n) oder in einigen Fällen logarithmisch linear o (n Protokoll n) im Eingang (sowohl in Raum als auch in Zeit). Algorithmen mit quadratischer Komplexität O (n2) Nicht skalieren, und selbst lineare Algorithmen verursachen Probleme, wenn sie wiederholt aufgerufen werden, und werden normalerweise nach Möglichkeit durch konstant oder logarithmisch ersetzt.

Über die asymptotische Wachstumsreihenfolge hinaus sind die konstanten Faktoren wichtig: Ein asymptotisch langsamerer Algorithmus kann schneller oder kleiner (weil einfacher) sein als ein asymptotisch schnellerer Algorithmus, wenn sie beide mit kleinen Eingaben konfrontiert sind, was in der Realität der Fall sein kann. Oft a Hybridalgorithmus wird aufgrund dieses Kompromisses mit der Größe die beste Leistung bieten.

Eine allgemeine Technik zur Verbesserung der Leistung besteht darin, Arbeiten zu vermeiden. Ein gutes Beispiel ist die Verwendung von a Schneller Weg In gemeinsamen Fällen Verbesserung der Leistung durch Vermeidung unnötiger Arbeiten. Verwenden Sie beispielsweise einen einfachen Textlayoutalgorithmus für lateinamerikanische Text Devanagari. Eine weitere wichtige Technik ist insbesondere das Caching Memoisierung, was redundante Berechnungen vermeidet. Aufgrund der Bedeutung des Caching gibt es in einem System häufig viele Ausgeschnittene, was zu Problemen aus dem Speichergebrauch und den Korrektheitsproblemen aus abgestandenen Caches führen kann.

Quellcode -Ebene

Über allgemeine Algorithmen und ihre Implementierung auf einer abstrakten Maschine können konkrete Auswahl der Quellcodeebene einen signifikanten Unterschied machen. Zum Beispiel bei frühen C -Compilern, während (1) war langsamer als zum(;;) für eine bedingungslose Schleife, weil während (1) bewertete 1 und hatte dann einen bedingten Sprung, der getestet wurde, wenn es wahr war, während zum (;;) hatte einen bedingungslosen Sprung. Einige Optimierungen (wie diese) können heutzutage von durchgeführt werden von Compiler optimieren. Dies hängt von der Quellsprache, der Zielmaschinensprache und dem Compiler ab und kann im Laufe der Zeit sowohl schwer zu verstehen als auch vorherzusagen und ändert sich. Dies ist ein wichtiger Ort, an dem das Verständnis von Compilern und Maschinencode die Leistung verbessern kann. Loop-Invariante-Codebewegung und Rückgabewertoptimierung sind Beispiele für Optimierungen, die den Bedarf an Hilfsvariablen verringern und sogar zu einer schnelleren Leistung führen können, indem es Optimierungen von Runden vermeiden.

Stufe aufbauen

Zwischen der Quelle und dem Kompilierebene, Richtlinien und Flaggen bauen Kann verwendet werden, um Leistungsoptionen im Quellcode und Compiler zu stimmen, wie z. B. die Verwendung Präprozessor Definiert, nicht benötigte Softwarefunktionen zu deaktivieren, für bestimmte Prozessormodelle oder Hardwarefunktionen zu optimieren oder vorherzusagen Verzweigung, zum Beispiel. Source-basierte Softwareverteilungssysteme wie z. BSD's Häfen und Gentoo's Portage kann diese Form der Optimierung nutzen.

Stufe kompilieren

Verwendung von an Compiler optimieren neigt dazu zu gewährleisten, dass die ausführbares Programm ist mindestens so viel optimiert, wie der Compiler vorhersagen kann.

Montagestufe

Auf der niedrigsten Ebene schreiben Code mit einem MontagespracheFür eine bestimmte Hardwareplattform kann der effizienteste und kompakteste Code erzeugt werden, wenn der Programmierer das vollständige Repertoire von nutzt Maschinenanweisungen. Viele Betriebssysteme benutzt auf eingebettete Systeme wurden aus diesem Grund traditionell in Assembler -Code geschrieben. Programme (außer sehr kleinen Programmen) werden aufgrund der Zeit und der Kosten selten von Anfang bis Ende in der Versammlung geschrieben. Die meisten werden von einer hochstufigen Sprache zur Montage zusammengestellt und von dort von Hand optimiert. Wenn Effizienz und Größe weniger wichtig sind, können große Teile in einer hochrangigen Sprache geschrieben werden.

Mit moderner Compiler optimieren und die größere Komplexität der jüngsten CPUsEs ist schwieriger, effizientere Code zu schreiben als das, was der Compiler generiert, und nur wenige Projekte benötigen diesen "ultimativen" Optimierungsschritt.

Viel Code, der heute geschrieben wurde, soll so viele Maschinen wie möglich ausgeführt werden. Infolgedessen nutzen Programmierer und Compiler nicht immer die effizienteren Anweisungen durch neuere CPUs oder Macken älterer Modelle. Darüber hinaus kann der für einen bestimmten Prozessor abgestimmte Assembly -Code ohne solche Anweisungen bei einem anderen Prozessor weiterhin suboptimal sein und erwartet eine andere Abstimmung des Codes.

In der Regel heute anstatt in der Versammlungssprache zu schreiben, verwenden Programmierer a Disassembler Analyse der Ausgabe eines Compilers und Ändern des hochrangigen Quellcodes, damit er effizienter kompiliert werden kann, oder zu verstehen, warum er ineffizient ist.

Laufzeit

Gerade rechtzeitig Compiler können maßgeschneiderten Maschinencode basierend auf Laufzeitdaten auf Kosten des Kompilierungsaufwandes erstellen. Diese Technik stammt vom frühesten regulären Ausdruck Motoren und ist mit Java Hotspot und V8 für JavaScript weit verbreitet. In manchen Fällen Adaptive Optimierung Möglicherweise können sie ausführen Laufzeit Optimierung, die die Fähigkeit von statischen Compilern überschreitet, indem Parameter dynamisch gemäß den tatsächlichen Eingaben oder anderen Faktoren eingestellt werden.

Profilgesteuerte Optimierung ist eine kompilierende Optimierungstechnik für die Time-Time (AOT), die auf Laufzeitprofilen basiert, und ähnelt einem statischen "Durchschnittsfall" -Analogon der dynamischen Technik der adaptiven Optimierung.

Selbstmodifizierender Code kann sich als Reaktion auf Laufzeitbedingungen ändern, um den Code zu optimieren. Dies war in Montage -Sprachprogrammen häufiger.

Etwas CPU -Designs Kann einige Optimierungen zur Laufzeit durchführen. Einige Beispiele sind Ausführende Ausführung, Spekulative Ausführung, Unterrichtspipelines, und Zweigprädiktoren. Compiler können das Programm beispielsweise durch diese CPU -Funktionen nutzen Anweisungsplanung.

Plattformabhängige und unabhängige Optimierungen

Die Codeoptimierung kann auch weitgehend als kategorisiert werden Plattform-Abhängige und plattformunabhängige Techniken. Während die letzteren auf den meisten oder allen Plattformen wirksam sind, verwenden plattformabhängige Techniken bestimmte Eigenschaften einer Plattform oder stützen sich je nach einzelnen Plattform oder sogar auf den einzelnen Prozessor auf Parameter. Das Schreiben oder Erstellen verschiedener Versionen desselben Codes für verschiedene Prozessoren kann daher erforderlich sein. Zum Beispiel sind plattformunabhängige Techniken bei der Optimierung auf Kompilierebene generische Techniken (wie z. Schlaufe abrollen, Reduzierung der Funktionsaufrufe, speichereffiziente Routinen, Verringerung der Bedingungen usw.), die sich auf die meisten CPU -Architekturen auf ähnliche Weise auswirken. Ein gutes Beispiel für eine plattformunabhängige Optimierung wurde bei der inneren Schleife gezeigt, wo beobachtet wurde, dass eine Schleife mit einer inneren für die Schleife mehr Berechnungen pro Zeiteinheit Zeit durchführt als eine Schleife ohne sie oder eine mit einer inneren während der Schleife.[2] Im Allgemeinen dienen diese dazu, die Gesamtsumme zu reduzieren Anweisungspfadlänge erforderlich, um das Programm zu vervollständigen und/oder die Gesamtspeicherverwendung während des Prozesses zu reduzieren. Auf der anderen Seite beinhalten plattformabhängige Techniken die Anweisungsplanung, Parallelität auf BefehlsebeneDie Parallelität auf Datenebene, Cache-Optimierungstechniken (d. H. Parameter, die zwischen verschiedenen Plattformen unterscheiden) und die optimale Anweisungsplanung können sich selbst bei verschiedenen Prozessoren derselben Architektur unterschiedlich unterscheiden.

Festigkeitsreduzierung

Computeraufgaben können auf verschiedene Weise mit unterschiedlicher Effizienz ausgeführt werden. Eine effizientere Version mit äquivalenter Funktionalität wird als bezeichnet Festigkeitsreduzierung. Betrachten Sie beispielsweise Folgendes C Code -Snippet, dessen Absicht es ist, die Summe aller Ganzzahlen von 1 bis zu erhalten N:

int i, Summe = 0; zum (i = 1; i <= N; ++i) {   Summe += i; } printf("Summe: %d\n", Summe); 

Dieser Code kann (unter der Annahme nein arithmetischer Überlauf) Mit einer mathematischen Formel wie folgt umgeschrieben werden:

int Summe = N * (1 + N) / 2; printf("Summe: %d\n", Summe); 

Die Optimierung, die manchmal automatisch von einem optimierenden Compiler durchgeführt wird, besteht darin, eine Methode auszuwählen (Algorithmus) Das ist rechnerisch effizienter, während die gleiche Funktionalität beibehält. Sehen Algorithmische Effizienz für eine Diskussion einiger dieser Techniken. Eine signifikante Verbesserung der Leistung kann jedoch häufig durch Entfernen von Fremdfunktionen erreicht werden.

Optimierung ist nicht immer ein offensichtlicher oder intuitiver Prozess. Im obigen Beispiel könnte die "optimierte" Version tatsächlich langsamer sein als die Originalversion, wenn N waren ausreichend klein und die jeweilige Hardware ist bei der Ergänzung viel schneller und viel schneller Schleifen Operationen als Multiplikation und Abteilung.

Kompromisse

In einigen Fällen beruht die Optimierung jedoch darauf, aufwändigere Algorithmen, "Sonderfälle" und spezielle "Tricks" zu verwenden und komplexe Kompromisse durchzuführen. Ein "vollständig optimiertes" Programm könnte schwieriger zu verstehen sein und daher möglicherweise mehr enthalten Fehler als nicht optimierte Versionen. Abgesehen von der Beseitigung offensichtlicher Antipattern verringern einige Optimierungen der Codeebene die Wartbarkeit.

Die Optimierung konzentriert sich im Allgemeinen darauf, nur ein oder zwei Aspekte der Leistung zu verbessern: Ausführungszeit, Speicherverbrauch, Speicherplatz, Bandbreite, Stromverbrauch oder andere Ressourcen. Dies erfordert normalerweise einen Kompromiss-wobei ein Faktor auf Kosten anderer optimiert wird. Zum Beispiel erhöhen Sie die Größe von Zwischenspeicher Verbessert die Laufzeitleistung, erhöht aber auch den Speicherverbrauch. Weitere gemeinsame Kompromisse sind Code-Klarheit und -versicht.

Es gibt Fälle, in denen der Programmierer, der die Optimierung durchführt, entscheiden muss, die Software für einige Vorgänge zu verbessern, jedoch auf Kosten der anderen Vorgänge weniger effizient. Diese Kompromisse können manchmal nicht technisch sein-z. B. wenn ein Konkurrent a veröffentlicht hat Benchmark Das Ergebnis muss geschlagen werden, um den kommerziellen Erfolg zu verbessern, aber vielleicht ist die Belastung, die Nutzung der Software weniger effizient zu verwenden. Solche Änderungen werden manchmal scherzhaft als als bezeichnet Pessimisationen.

Engpässe

Die Optimierung kann das Finden a beinhalten Engpass In einem System - eine Komponente, die der begrenzende Faktor für die Leistung ist. In Bezug auf den Code wird dies oft a sein Hot Spot- Ein kritischer Teil des Codes, der der Hauptverbraucher der erforderlichen Ressource ist - obwohl es sich um einen weiteren Faktor handelt, wie z. B. I/O -Latenz oder Netzwerkbandbreite.

In der Informatik folgt der Ressourcenkonsum häufig einer Form von Machtgesetz Verteilung und die Pareto -Prinzip kann auf die Ressourcenoptimierung angewendet werden, indem beobachtet wird, dass 80% der Ressourcen in der Regel von 20% der Operationen verwendet werden.[3] In der Software -Engineering ist es häufig eine bessere Annäherung, dass 90% der Ausführungszeit eines Computerprogramms für die Ausführung von 10% des Code (in diesem Zusammenhang als 90/10 Gesetz bezeichnet) ausgegeben werden.

Komplexere Algorithmen und Datenstrukturen funktionieren gut mit vielen Elementen, während einfache Algorithmen für kleine Datenmengen besser geeignet sind - die Einrichtung, die Initialisierungszeit und die konstanten Faktoren des komplexeren Algorithmus können den Nutzen und damit a überwiegen. Hybridalgorithmus oder Adaptiver Algorithmus Kann schneller sein als jeder einzelne Algorithmus. Ein Leistungsprofiler kann verwendet werden, um Entscheidungen darüber einzugrenzen, welche Funktionen für welche Bedingungen passt.[4]

In einigen Fällen mehr hinzufügen Erinnerung Kann helfen, ein Programm schneller zu machen. Beispielsweise wird ein Filterprogramm beispielsweise jede Zeile und jeden Filter gelesen und diese Zeile sofort ausgeben. Dies verwendet nur genügend Speicher für eine Zeile, aber die Leistung ist aufgrund der Latenz der einzelnen Festplatten in der Regel schlecht. Das zwischengespeicherte Ergebnis ist ähnlich effektiv, erfordert jedoch auch einen größeren Speicherverbrauch.

Wann zu optimieren

Optimierung kann sich verringern Lesbarkeit und fügen Sie Code hinzu, der nur verwendet wird, um die zu verbessern Leistung. Dies kann Programme oder Systeme erschweren und es schwieriger machen, sie zu pflegen und zu debuggen. Infolgedessen wird häufig am Ende der Optimierung oder Leistungsstimmung durchgeführt Entwicklungsstadium.

Donald Knuth machte die folgenden zwei Aussagen zur Optimierung:

"Wir sollten kleine Effizienz vergessen, sagen wir in ungefähr 97% der Fälle: Frühgeborene Optimierung ist die Wurzel aller Bösen. Doch wir sollten unsere Chancen in diesen kritischen 3% nicht ergeben."[5]

(Er schrieb auch das Zitat zu, auf Tony Hoare mehrere Jahre später,[6] Obwohl dies ein Fehler gewesen sein könnte, wie Hoare den Ausdruck geprägt hat.[7])

"In etablierten Ingenieurdisziplinen gilt eine Verbesserung von 12%, die leicht erhalten wird, niemals als marginal angesehen, und ich glaube, dass der gleiche Standpunkt in der Software -Engineering herrschen sollte."[5]

"Frühgeborene Optimierung" ist eine Phrase, die verwendet wird, um eine Situation zu beschreiben, in der ein Programmierer Leistungsüberlegungen auf das Design eines Code -Stücks beeinflussen können. Dies kann zu einem Design führen, das nicht so sauber ist, wie er hätte sein können, oder Code, der falsch ist, da der Code durch die Optimierung kompliziert ist und der Programmierer durch Optimierung abgelenkt wird.

Bei der Entscheidung, ob Sie einen bestimmten Teil des Programms optimieren sollen, Amdahls Gesetz sollte immer berücksichtigt werden: Die Auswirkungen auf das Gesamtprogramm hängen sehr davon ab, wie viel Zeit tatsächlich in diesem speziellen Teil aufgewendet wird, was nicht immer aus der Betrachtung des Codes ohne einen klar ist Performance-Analyse.

Ein besserer Ansatz ist daher, zuerst zu entwerfen, aus dem Design zu codieren und dann Profil/Benchmark Der resultierende Code, um zu sehen, welche Teile optimiert werden sollten. Ein einfaches und elegantes Design ist in dieser Phase oft einfacher zu optimieren, und die Profilierung kann unerwartete Leistungsprobleme aufdecken, die nicht durch vorzeitige Optimierung angegangen worden wären.

In der Praxis ist es oft notwendig, die Leistungsziele beim ersten Entwerfen von Software im Auge zu behalten, aber der Programmierer gleicht die Ziele von Design und Optimierung aus.

Moderne Compiler und Betriebssysteme sind so effizient, dass die beabsichtigte Leistung häufig nicht zutrifft. Beispielsweise führt das zwischengespeicherte Daten auf Anwendungsebene, die erneut auf Betriebssystemebene zwischenstrahlt werden, keine Verbesserungen der Ausführung zu. Trotzdem ist es ein seltener Fall, wenn der Programmierer fehlgeschlagene Optimierungen aus dem Produktionscode entfernt. Es ist auch wahr, dass Fortschritte in der Hardware meistens potenzielle Verbesserungen vermeiden, aber der verdeckte Code bleibt in Zukunft, lange nachdem sein Zweck negiert wurde.

Makros

Optimierung während der Codeentwicklung mit Verwendung Makros nimmt verschiedene Formen in verschiedenen Sprachen an.

In einigen prozeduralen Sprachen, wie z. C und C ++, Makros werden mit Token -Substitution implementiert. Heutzutage, Inline -Funktionen kann als als verwendet werden Geben Sie sicher Alternative in vielen Fällen. In beiden Fällen kann die eingefügte Funktionsbehörde dann vom Compiler, einschließlich weiter Konstante Faltung, was einige Berechnungen verschieben kann, um die Zeit zu kompilieren.

In vielen Funktionelle Programmierung Sprachen-Makros werden unter Verwendung von Parse-Time-Substitution von analysenden Bäumen/abstrakten Syntaxbäumen implementiert, von denen sie behauptet, dass sie sie sicherer zu verwenden haben. Da in vielen Fällen eine Interpretation verwendet wird, ist dies eine Möglichkeit, sicherzustellen, dass solche Berechnungen nur zur Zeit von Parse-Zeit und manchmal nur auf die einzige Weise durchgeführt werden.

Lispeln Ursprung dieser Makrostil, und solche Makros werden oft als "Lisp-ähnliche Makros" bezeichnet. Ein ähnlicher Effekt kann durch Verwendung erreicht werden Vorlage Metaprogrammierung in C ++.

In beiden Fällen wird die Arbeit in die Kompilierung der Zeit verschoben. Der Unterschied zwischen C Makros auf einer Seite und Lisp-ähnliche Makros und C ++ Vorlage Metaprogrammierung Auf der anderen Seite ermöglichen die letzteren Tools die Durchführung willkürlicher Berechnungen bei Compile-Time/Parse-Zeit, während die Erweiterung von C Makros führt keine Berechnung durch und stützt sich auf die Optimierer -Fähigkeit, sie auszuführen. Zusätzlich, C Makros unterstützen nicht direkt Rekursion oder Wiederholung, auch nicht Turing vollständig.

Wie bei jeder Optimierung ist es jedoch oft schwierig vorherzusagen, wo solche Tools den größten Einfluss haben, bevor ein Projekt abgeschlossen ist.

Automatisierte und manuelle Optimierung

Siehe auch Kategorie: Compiler -Optimierungen

Die Optimierung kann von Compilern automatisiert oder von Programmierern durchgeführt werden. Die Gewinne sind in der Regel begrenzt für die lokale Optimierung und größer für globale Optimierungen. Normalerweise besteht die stärkste Optimierung darin, einen Vorgesetzten zu finden Algorithmus.

Die Optimierung eines ganzen Systems wird normalerweise von Programmierern durchgeführt, da es für automatisierte Optimierer zu komplex ist. In dieser Situation ändern Programmierer oder Systemadministratoren explizit den Code, sodass das Gesamtsystem eine bessere Leistung erbringt. Obwohl es zu einer besseren Effizienz führen kann, ist es weitaus teurer als automatisierte Optimierungen. Da viele Parameter die Programmleistung beeinflussen, ist der Programmoptimierungsraum groß. Meta-Heuristiken und maschinelles Lernen werden verwendet, um die Komplexität der Programmoptimierung zu begehen.[8]

Verwenden ein Profiler (oder Leistungsanalysator) um die Abschnitte des Programms zu finden, die die meisten Ressourcen einnehmen - die Engpass. Programmierer glauben manchmal, dass sie eine klare Vorstellung davon haben, wo sich der Engpass befindet, aber die Intuition ist häufig falsch. Die Optimierung eines unwichtigen Code -Stücks ist in der Regel wenig dazu beigetragen, die Gesamtleistung zu unterstützen.

Wenn der Engpass lokalisiert ist, beginnt die Optimierung normalerweise mit dem Überdenken des im Programm verwendeten Algorithmus. Meistens kann ein bestimmter Algorithmus spezifisch auf ein bestimmtes Problem zugeschnitten werden, was eine bessere Leistung als ein generischer Algorithmus ergibt. Zum Beispiel wird die Aufgabe, eine riesige Liste von Elementen zu sortieren schnelle Sorte Routine, eine der effizientesten generischen Algorithmen. Wenn jedoch einige Merkmale der Elemente ausnutzbar sind (z. B. sind sie bereits in einer bestimmten Reihenfolge angeordnet), kann eine andere Methode verwendet werden oder sogar eine maßgefertigte Sortierroutine.

Nachdem der Programmierer einigermaßen sicher ist, dass der beste Algorithmus ausgewählt ist, kann die Codeoptimierung beginnen. Schleifen können abgerollt werden (für den überdischen Schleifen überziehen, obwohl dies oft zu dem führen kann niedriger Geschwindigkeit, wenn es das überlastet CPU -Cache), so kleine Datentypen können verwendet werden, eine ganzzahlige Arithmetik kann anstelle von Gleitpunkt verwendet werden und so weiter. (Sehen Algorithmische Effizienz Artikel für diese und andere Techniken.)

Leistung Engpässe können eher auf Spracheinschränkungen als auf Algorithmen oder Datenstrukturen im Programm zurückzuführen sein. Manchmal kann ein kritischer Teil des Programms in einem anderen neu geschrieben werden Programmiersprache Das gibt einen direkteren Zugriff auf die zugrunde liegende Maschine. Zum Beispiel ist es für sehr üblich hohes Level Sprachen mögen Python Module geschrieben haben C für größere Geschwindigkeit. Programme, die bereits in C geschrieben wurden, können Module geschrieben haben Montage. Programme geschrieben in D kann das verwenden Inline -Assembler.

Die Umschreibung von Abschnitten "zahlt" unter diesen Umständen wegen eines Generales ab "Faustregel"bekannt als das 90/10 -Gesetz, das besagt, dass 90% der Fälle in 10% des Kodex ausgegeben werden, und nur 10% der Fälle in den verbleibenden 90% des Kodex. Ein kleiner Teil des Programms kann einen enormen Einfluss auf die Gesamtgeschwindigkeit haben - wenn sich die richtigen Teils (en) befinden können.

Die manuelle Optimierung hat manchmal den Nebeneffekt der Untergrabung der Lesbarkeit. Daher sollten Code-Optimierungen sorgfältig dokumentiert werden (vorzugsweise unter Verwendung von Inline-Kommentaren) und ihre Auswirkungen auf die zukünftige Entwicklung bewertet.

Das Programm, das eine automatisierte Optimierung durchführt Optimierer. Die meisten Optimierer sind in Compiler eingebettet und arbeiten während der Zusammenstellung. Optimierer können den generierten Code häufig an bestimmte Prozessoren anpassen.

Heute sind automatisierte Optimierungen fast ausschließlich beschränkt auf Compiler -Optimierung. Da Compiler-Optimierungen jedoch normalerweise auf einen festen Satz von eher allgemeinen Optimierungen beschränkt sind, besteht eine erhebliche Nachfrage nach Optimierern, die Beschreibungen von Problemen und sprachspezifischen Optimierungen akzeptieren können, sodass ein Ingenieur benutzerdefinierte Optimierungen angeben kann. Tools, die Beschreibungen von Optimierungen akzeptieren, werden aufgerufen Programmumwandlung Systeme und werden beginnen, auf reale Softwaresysteme wie C ++ angewendet zu werden.

Einige hochrangige Sprachen (Eiffel, Esterel) optimieren Sie ihre Programme, indem Sie eine verwenden Zwischensprache.

Raster Computing oder verteiltes Computer Ziel ist es, das gesamte System zu optimieren, indem Aufgaben von Computern mit hoher Verwendung auf Computer mit Leerlaufzeit bewegt werden.

Zeit für die Optimierung benötigt

Manchmal kann die Zeit, die für eine Optimierung in selbst benötigt wird, ein Problem sein.

Die Optimierung des vorhandenen Codes fügt normalerweise keine neuen Funktionen hinzu, und schlimmer noch, er kann neue hinzufügen Käfer in zuvor funktionierendem Code (wie jede Änderung könnte). Da manuell optimierter Code manchmal weniger "Lesbarkeit" hat als ein nicht optimierter Code, kann sich die Optimierung auch auf die Wartbarkeit auswirken. Die Optimierung hat einen Preis und es ist wichtig, dass sich die Investition lohnt.

Ein automatischer Optimierer (oder Compiler optimierenEin Programm, das die Codeoptimierung durchführt) muss selbst optimiert werden, um entweder die Effizienz seiner Zielprogramme weiter zu verbessern oder seinen eigenen Betrieb zu beschleunigen. Eine Zusammenstellung, die mit Optimierung "eingeschaltet" durchgeführt wird, dauert normalerweise länger, obwohl dies normalerweise nur ein Problem ist, wenn die Programme ziemlich groß sind.

Insbesondere für Just-in-Time-Compiler die Leistung der Laufzeit Kompilierkomponente, die zusammen mit seinem Zielcode ausführt, ist der Schlüssel zur Verbesserung der Gesamtausführungsgeschwindigkeit.

Verweise

  • Jon Bentley: Schreiben effizienter Programme, ISBN0-13-970251-2.
  • Donald Knuth: Die Kunst der Computerprogrammierung
  1. ^ Robert Sedgebick, Algorithmen, 1984, p. 84.
  2. ^ Adewumi, Tosin P. (2018-08-01). "Inner Loop -Programmkonstrukt: Ein schnellerer Weg für die Programmausführung". Öffnen Sie die Informatik. 8 (1): 115–122. doi:10.1515/comp-2018-0004.
  3. ^ Wescott, Bob (2013). Das jedes Computer Performance -Buch, Kapitel 3: Nützliche Gesetze. CreateSpace. ISBN 978-1482657753.
  4. ^ "Performance -Profilerstellung mit Fokus". Abgerufen 15. August 2017.
  5. ^ a b Knuth, Donald (Dezember 1974). "Strukturiertes Programmieren mit Go to Aussagen". ACM Computing -Umfragen. 6 (4): 268. Citeseerx 10.1.1.103.6084. doi:10.1145/356635.356640. S2CID 207630080.
  6. ^ Die Fehler von Tex, in Software - Praktizierung & Erfahrung, Band 19, Ausgabe 7 (Juli 1989), S. 607–685, nachgedruckt in seinem Buch Literate Programing (S. 276).
  7. ^ "Frühgeborene Optimierung ist die Wurzel des gesamten Bösen". hans.gerwitz.com. Abgerufen 2020-12-18. Hoare hat es jedoch nicht behauptet, als ich ihn im Januar 2004 fragte
  8. ^ Memeti, Suejb; Pllana, Sabri; Binotto, Alécio; Kołodziej, Joanna; Brandic, Ivona (26. April 2018). "Verwendung von Meta-Heuristiken und maschinellem Lernen für die Softwareoptimierung paralleler Computersysteme: eine systematische Literaturübersicht". Computer. Springer Wien. 101 (8): 893–936. Arxiv:1801.09444. Bibcode:2018ArXIV180109444m. doi:10.1007/s00607-018-0614-9. S2CID 13868111.

Externe Links