Während ich auf diese Frage antwortete , begann ich mich zu fragen, warum so viele Entwickler der Meinung sind, dass ein gutes Design nicht für die Leistung verantwortlich sein sollte, da dies die Lesbarkeit und / oder Wartbarkeit beeinträchtigen würde.
Ich glaube, dass ein gutes Design auch die Leistung zum Zeitpunkt der Erstellung berücksichtigt und dass ein guter Entwickler mit einem guten Design ein effizientes Programm schreiben kann, ohne die Lesbarkeit oder Wartbarkeit zu beeinträchtigen.
Ich gebe zwar zu, dass es extreme Fälle gibt, aber warum bestehen viele Entwickler darauf, dass ein effizientes Programm / Design zu schlechter Lesbarkeit und / oder schlechter Wartbarkeit führt und die Leistung daher nicht in Betracht gezogen werden sollte?
Antworten:
Ich denke, solche Ansichten sind normalerweise Reaktionen auf Versuche zur vorzeitigen (Mikro-) Optimierung , die immer noch vorherrschen und in der Regel mehr schaden als nützen. Wenn man versucht, solchen Ansichten entgegenzuwirken, fällt man leicht in das andere Extrem - oder sieht zumindest so aus.
Es ist jedoch richtig, dass mit der enormen Entwicklung der Hardwareressourcen in den letzten Jahrzehnten für die meisten der heute geschriebenen Programme die Leistung nicht mehr einschränkend wirkte. Natürlich sollte man die erwartete und erreichbare Leistung während der Entwurfsphase berücksichtigen, um die Fälle zu identifizieren , in denen die Leistung ein Hauptproblem sein kann . Und dann ist es in der Tat wichtig, von Anfang an auf Leistung zu achten. Die allgemeine Einfachheit, Lesbarkeit und Wartbarkeit ist jedoch immer noch wichtiger . Wie bereits erwähnt, ist leistungsoptimierter Code komplexer, schwerer zu lesen und zu warten und fehleranfälliger als die einfachste funktionierende Lösung. Daher muss jeder Optimierungsaufwand bewiesen werden - nicht nur geglaubt- echte Vorteile zu erzielen und gleichzeitig die langfristige Wartbarkeit des Programms so wenig wie möglich zu beeinträchtigen. So isoliert ein gutes Design die komplexen, leistungsintensiven Teile vom Rest des Codes , der so einfach und sauber wie möglich gehalten wird.
quelle
Bei Ihrer Frage von Seiten eines Entwicklers, der an Hochleistungscode arbeitet, gibt es mehrere Dinge, die beim Entwurf berücksichtigt werden müssen.
Mach es richtig, mach es schön, mach es schnell. In dieser Reihenfolge.
quelle
contains
, verwenden Sie aHashSet
, nicht anArrayList
. Die Leistung mag keine Rolle spielen, aber es gibt keinen Grund, dies nicht zu tun. Nutzen Sie die Übereinstimmungen zwischen gutem Design und Leistung - wenn Sie eine Sammlung bearbeiten, versuchen Sie, alles in einem einzigen Durchgang zu erledigen, der wahrscheinlich besser lesbar und (wahrscheinlich) schneller ist.Wenn ich davon ausgehen kann, dass ich @ greengits nettes Diagramm "ausleihen" und eine kleine Ergänzung machen kann:
Uns allen wurde "beigebracht", dass es Kompromisskurven gibt. Außerdem haben wir alle angenommen, dass wir so optimale Programmierer sind, dass jedes Programm, das wir schreiben, so knapp ist, dass es auf der Kurve liegt . Befindet sich ein Programm in der Kurve, verursacht jede Verbesserung in einer Dimension notwendigerweise Kosten in der anderen Dimension.
Nach meiner Erfahrung kommen Programme nur dann in die Nähe einer Kurve, wenn sie gestimmt, optimiert, gehämmert, gewachst und im Allgemeinen in "Codegolf" umgewandelt werden. Die meisten Programme bieten in allen Dimensionen viel Raum für Verbesserungen. Hier ist was ich meine.
quelle
Gerade weil leistungsfähige Softwarekomponenten in der Regel um Größenordnungen komplexer sind als andere Softwarekomponenten (alle anderen Dinge sind gleich).
Selbst dann ist es nicht so eindeutig, wenn Leistungsmetriken eine kritisch wichtige Anforderung sind, ist es unerlässlich, dass das Design eine Komplexität aufweist, um solchen Anforderungen gerecht zu werden. Die Gefahr ist ein Entwickler, der einen Sprint mit einem relativ einfachen Feature verschwendet, das versucht, ein paar zusätzliche Millisekunden aus seiner Komponente herauszuholen.
Unabhängig davon steht die Komplexität des Designs in direktem Zusammenhang mit der Fähigkeit eines Entwicklers, ein solches Design schnell zu erlernen und sich damit vertraut zu machen. Weitere Änderungen an der Funktionalität einer komplexen Komponente können zu Fehlern führen, die möglicherweise nicht von Komponententests erfasst werden. Komplexe Entwürfe haben viele weitere Facetten und mögliche Testfälle, um das Ziel einer 100% igen Testabdeckung noch mehr zu verwirklichen.
Vor diesem Hintergrund sollte angemerkt werden, dass eine schlecht funktionierende Softwarekomponente schlecht funktionieren kann, nur weil sie dumm geschrieben und unnötig komplex ist, basierend auf der Unkenntnis des ursprünglichen Autors (8 Datenbankaufrufe, um eine einzelne Entität zu erstellen, wenn nur eine dies tun würde) , völlig unnötiger Code, der unabhängig davon zu einem einzigen Codepfad führt, etc ...) In diesen Fällen geht es eher darum, die Codequalität zu verbessern und die Leistung zu steigern, die als Folge des Refaktors und NICHT der beabsichtigten Konsequenz erforderlich ist.
Vorausgesetzt, eine gut gestaltete Komponente ist immer weniger komplex als eine ähnlich gut gestaltete, auf Leistung abgestimmte Komponente (alle anderen Dinge sind gleich).
quelle
Es ist nicht so sehr, dass diese Dinge nicht koexistieren können. Das Problem ist, dass der Code aller Benutzer bei der ersten Iteration langsam, unleserlich und nicht verwaltbar ist. Der Rest der Zeit wird für die Verbesserung des Wichtigsten aufgewendet. Wenn das Leistung ist, dann machen Sie es. Schreiben Sie keinen schrecklichen Code, aber wenn es nur X-schnell sein muss, dann machen Sie es X-schnell. Ich glaube, dass Leistung und Sauberkeit im Grunde genommen nichts miteinander zu tun haben. Performanter Code verursacht keinen hässlichen Code. Wenn Sie jedoch Ihre Zeit damit verbringen, jeden Teil des Codes schnell zu optimieren, raten Sie mal, was Sie nicht getan haben? Machen Sie Ihren Code sauber und wartbar.
quelle
Wie du siehst...
Leistung und Lesbarkeit sind also nur bescheiden miteinander verbunden - und in den meisten Fällen gibt es keine wirklich großen Anreize, die ersteren gegenüber letzteren vorzuziehen. Und ich spreche hier über Hochsprachen.
quelle
Meiner Meinung nach sollte Leistung eine Überlegung sein, wenn es sich um ein tatsächliches Problem (oder z. B. eine Anforderung) handelt. Wenn Sie dies nicht tun, kann dies zu Mikrooptimierungen führen, die dazu führen können, dass der Code nur vereinzelt verschleiert wird, um hier und da ein paar Mikrosekunden zu sparen, was wiederum zu weniger wartbarem und weniger lesbarem Code führt. Stattdessen sollte man sich bei Bedarf auf die tatsächlichen Engpässe des Systems konzentrieren und die Leistung dort hervorheben.
quelle
Der Punkt ist nicht die Lesbarkeit sollte immer die Effizienz übertreffen. Wenn Sie von Anfang an wissen, dass Ihr Algorithmus hocheffizient sein muss, wird er einer der Faktoren sein, die Sie für die Entwicklung verwenden.
Die Sache ist, dass die meisten Anwendungsfälle keinen schnellen Code benötigen. In vielen Fällen verursachen E / A- oder Benutzerinteraktionen wesentlich mehr Verzögerungen als die Ausführung Ihres Algorithmus. Der Punkt ist, dass Sie sich nicht die Mühe machen sollten, etwas effizienter zu machen, wenn Sie nicht wissen, dass es der Flaschenhals ist.
Durch die Optimierung des Codes für die Leistung wird dies häufig komplizierter, da die Dinge im Allgemeinen clever und nicht intuitiv ausgeführt werden. Komplizierterer Code ist schwieriger zu warten und für andere Entwickler schwieriger zu erfassen (beides sind Kosten, die berücksichtigt werden müssen). Gleichzeitig können Compiler häufig vorkommende Fälle sehr gut optimieren. Es ist möglich, dass Ihr Versuch, einen allgemeinen Fall zu verbessern, dazu führt, dass der Compiler das Muster nicht mehr erkennt und Ihnen daher nicht helfen kann, Ihren Code schnell zu machen. Es sollte beachtet werden, dass dies nicht bedeutet, dass Sie schreiben, was Sie wollen, ohne sich um die Leistung zu kümmern. Sie sollten nichts tun, was eindeutig ineffizient ist.
Es geht darum, sich keine Sorgen um kleine Dinge zu machen, die die Dinge verbessern könnten . Verwenden Sie einen Profiler und sehen Sie, dass 1) das, was Sie jetzt haben, ein Problem ist und 2) das, worauf Sie es geändert haben, eine Verbesserung war.
quelle
Ich denke, die meisten Programmierer haben dieses Bauchgefühl, einfach weil der Performance-Code die meiste Zeit auf viel mehr Informationen basiert (über den Kontext, Hardware-Kenntnisse, globale Architektur) als jeder andere Code in Anwendungen. Der meiste Code drückt nur einige Lösungen für bestimmte Probleme aus, die in einigen Abstraktionen modular eingekapselt sind (z. B. Funktionen), und dies bedeutet, dass das Wissen über den Kontext nur auf das beschränkt wird, was in diese Kapselung eingeht (z. B. Funktionsparameter).
Wenn Sie für eine hohe Leistung schreiben, gehen Sie, nachdem Sie algorithmische Optimierungen korrigiert haben, auf Details ein, die weitaus mehr Kenntnisse über den Kontext erfordern. Das könnte natürlich jeden Programmierer überwältigen, der sich für die Aufgabe nicht fokussiert genug fühlt.
quelle
Denn die Kosten für die globale Erwärmung (aufgrund der zusätzlichen CPU-Zyklen, die durch Hunderte Millionen PCs und massive Rechenzentrumsfunktionen skaliert werden) und die mittelmäßige Akkulaufzeit (auf den Mobilgeräten der Benutzer), die für die Ausführung ihres schlecht optimierten Codes erforderlich sind, treten bei den meisten nur selten auf Programmierer Leistung oder Peer-Reviews.
Es ist eine ökonomisch negative Externalität, ähnlich einer Form ignorierter Umweltverschmutzung. Das Kosten-Nutzen-Verhältnis, wenn man überhaupt über Leistung nachdenkt, ist also mental von der Realität abgewichen.
Hardware-Entwickler haben hart daran gearbeitet, den neuesten CPUs Funktionen für Stromsparmodus und Taktskalierung hinzuzufügen. Es liegt an den Programmierern, die Hardware diese Funktionen häufiger nutzen zu lassen, indem sie nicht jeden verfügbaren CPU-Takt zerkaut.
HINZUGEFÜGT: In früheren Zeiten waren die Kosten für einen Computer Millionen, daher war die Optimierung der CPU-Zeit sehr wichtig. Dann wurden die Kosten für die Entwicklung und Pflege des Codes höher als die Kosten für die Computer, so dass die Optimierung im Vergleich zur Produktivität des Programmierers in Ungnade fiel. Jetzt jedoch werden andere Kosten höher als die Kosten für Computer, und die Kosten für die Stromversorgung und Kühlung all dieser Rechenzentren werden höher als die Kosten aller Prozessoren im Inneren.
quelle
Ich denke, es ist schwer, alle drei zu erreichen. Zwei, denke ich, können machbar sein. Zum Beispiel denke ich, dass es in einigen Fällen möglich ist, Effizienz und Lesbarkeit zu erreichen, aber die Wartbarkeit kann mit mikrooptimiertem Code schwierig sein. Der effizienteste Code auf dem Planeten fehlt im Allgemeinen sowohl die Wartbarkeit und Lesbarkeit als wahrscheinlich offensichtlich für die meisten ist, es sei denn , Sie die Art sind, die die Hand SoA-vektorisiert verstehen kann, multithreaded SIMD - Code , dass Intel mit inlined schreibt Montage oder den cutting -edge-Algorithmen, die in der Industrie mit 40-seitigen mathematischen Artikeln verwendet werden, die erst vor zwei Monaten veröffentlicht wurden, und 12 Bibliotheken mit Code für eine unglaublich komplexe Datenstruktur.
Mikroeffizienz
Eine Sache, die meiner Meinung nach der gängigen Meinung widerspricht, ist, dass der intelligenteste algorithmische Code oft schwieriger zu warten ist als der am besten abgestimmte, unkomplizierte Algorithmus. Diese Idee, dass Verbesserungen der Skalierbarkeit im Vergleich zu mikrooptimiertem Code (z. B. cachefreundliche Zugriffsmuster, Multithreading, SIMD usw.) mehr Erfolg bringen, würde ich in Frage stellen, zumindest wenn ich in einer Branche mit extrem komplexen Branchen gearbeitet habe Datenstrukturen und -algorithmen (die Visual FX-Branche), insbesondere in Bereichen wie der Netzverarbeitung, weil der Knall zwar groß sein kann, aber der Preis extrem hoch ist, wenn Sie neue Algorithmen und Datenstrukturen einführen, von denen noch niemand etwas gehört hat, seit sie Marken sind Neu. Weiter, ich '
Diese Vorstellung, dass algorithmische Optimierungen beispielsweise Optimierungen im Zusammenhang mit Speicherzugriffsmustern immer übertreffen, stimmte mir also nicht ganz zu. Natürlich, wenn Sie eine Blasensorte verwenden, kann Ihnen dort keine Mikrooptimierung weiterhelfen ... aber innerhalb der Vernunft denke ich nicht, dass es immer so eindeutig ist. Und algorithmische Optimierungen sind wahrscheinlich schwieriger zu pflegen als Mikrooptimierungen. Ich finde es viel einfacher, beispielsweise Intels Embree zu warten, das einen klassischen und einfachen BVH-Algorithmus verwendet und den Mist nur im Mikromaßstab herausarbeitet, als Dreamworks OpenVDB-Code für innovative Methoden zur algorithmischen Beschleunigung der Flüssigkeitssimulation. Zumindest in meiner Branche würde ich gerne mehr Leute sehen, die mit der Mikrooptimierung von Computerarchitekturen vertraut sind, so wie es Intel getan hat, als sie in die Szene eingetreten sind. im Gegensatz zu Tausenden und Abertausenden neuer Algorithmen und Datenstrukturen. Mit effektiven Mikrooptimierungen könnten Menschen möglicherweise immer weniger Gründe finden, neue Algorithmen zu erfinden.
Ich habe zuvor in einer alten Codebasis gearbeitet, in der fast jeder einzelne Benutzervorgang über eine eigene Datenstruktur und einen eigenen Algorithmus verfügte (insgesamt Hunderte exotischer Datenstrukturen). Und die meisten von ihnen hatten sehr verzerrte Leistungsmerkmale und waren sehr eng anwendbar. Es wäre so viel einfacher gewesen, wenn sich das System um ein paar Dutzend allgemeiner anwendbare Datenstrukturen drehen könnte, und ich denke, dass dies der Fall gewesen wäre, wenn sie viel besser mikrooptimiert worden wären. Ich erwähne diesen Fall, weil die Mikrooptimierung in einem solchen Fall möglicherweise die Wartbarkeit erheblich verbessern kann, wenn es sich um den Unterschied zwischen Hunderten von mikropessimierten Datenstrukturen handelt, die nicht einmal sicher für reine Lesezwecke verwendet werden können, bei denen Cachefehler übrig bleiben richtig gegen
Funktionssprachen
In der Zwischenzeit war einer der wartbarsten Codes, die mir je begegnet sind, einigermaßen effizient, aber äußerst schwer zu lesen, da sie in funktionalen Sprachen geschrieben waren. Im Allgemeinen sind Lesbarkeit und Wartungsfreundlichkeit meiner Meinung nach widersprüchliche Vorstellungen.
Es ist wirklich schwierig, Code gleichzeitig lesbar, wartbar und effizient zu machen. In der Regel müssen Sie bei einem dieser drei Punkte Kompromisse eingehen, wenn nicht bei zwei, z. B. bei der Lesbarkeit oder bei der Wartbarkeit, um die Effizienz zu beeinträchtigen. Es ist in der Regel Wartbarkeit, die leidet, wenn Sie viele der beiden anderen suchen.
Lesbarkeit vs. Wartbarkeit
Nun, wie gesagt, glaube ich, dass Lesbarkeit und Wartbarkeit keine harmonischen Konzepte sind. Schließlich bildet der für die meisten von uns Sterblichen am besten lesbare Code sehr intuitiv menschliche Gedankenmuster ab, und menschliche Gedankenmuster sind von Natur aus fehleranfällig: " Wenn dies geschieht, tun Sie dies. Wenn dies geschieht, tun Sie dies. Andernfalls tun Sie dies. Hoppla Ich habe etwas vergessen! Wenn diese Systeme miteinander interagieren, sollte dies geschehen, damit dieses System dies tun kann ... Oh, was ist mit diesem System, wenn dieses Ereignis ausgelöst wird?"Ich habe das genaue Zitat vergessen, aber jemand hat einmal gesagt, wenn Rom wie eine Software gebaut wäre, müsste nur ein Vogel auf einer Wand landen, um es zum Umfallen zu bringen. Dies ist bei den meisten Programmen der Fall. Es ist zerbrechlicher, als wir es oft tun." Ein paar Zeilen scheinbar harmlosen Codes könnten hier und da dazu führen, dass wir das gesamte Design überdenken, und Hochsprachen, die so gut wie möglich lesbar sein sollen, sind keine Ausnahmen für solche menschlichen Designfehler .
Reine funktionale Sprachen sind dem so nahe wie möglich (nicht annähernd unverwundbar, aber relativ viel näher als die meisten). Und das liegt zum Teil daran, dass sie sich nicht intuitiv auf das menschliche Denken abbilden lassen. Sie sind nicht lesbar. Sie zwingen uns Denkmuster auf, die es uns ermöglichen, Probleme mit möglichst wenigen Sonderfällen mit möglichst geringem Wissen und ohne Nebenwirkungen zu lösen. Sie sind extrem orthogonal und ermöglichen es, den Code häufig zu ändern und zu ändern, ohne dass Überraschungen auftreten, die so episch sind, dass wir das Design auf einem Zeichenbrett überdenken müssen, bis wir unsere Meinung über das Gesamtdesign ändern müssen, ohne alles neu zu schreiben. Es scheint nicht einfacher zu sein, als das zu warten ... aber der Code ist immer noch sehr schwer zu lesen,
quelle
Ein Problem ist, dass die begrenzte Zeit für Entwickler bedeutet, dass Sie sich nicht mehr mit den anderen Problemen befassen müssen, um sie zu optimieren.
Es gibt ein ziemlich gutes Experiment, auf das in Meyers Code Complete verwiesen wird. Verschiedene Entwicklergruppen wurden gebeten, die Geschwindigkeit, Speichernutzung, Lesbarkeit, Robustheit usw. zu optimieren. Es stellte sich heraus, dass ihre Projekte in allen Punkten, in denen sie optimiert werden sollten, eine hohe Punktzahl erzielten, in allen anderen Qualitäten jedoch eine geringere.
quelle
Weil erfahrene Programmierer gelernt haben, dass es wahr ist.
Wir haben mit schlankem Code gearbeitet, der keine Leistungsprobleme aufweist.
Wir haben an einer Menge Code gearbeitet, der zur Behebung von Leistungsproblemen SEHR komplex ist.
Ein unmittelbares Beispiel ist, dass mein letztes Projekt 8.192 manuell gesplittete SQL-Tabellen enthielt. Dies wurde aufgrund von Leistungsproblemen benötigt. Das Setup für die Auswahl aus einer Tabelle ist viel einfacher als für die Auswahl und Verwaltung von 8.192 Shards.
quelle
Es gibt auch einige berühmte Teile von hochoptimiertem Code, die das Gehirn der meisten Menschen beugen und den Fall unterstützen, dass hochoptimierter Code schwer zu lesen und zu verstehen ist.
Hier ist der berühmteste, denke ich. Entnommen aus der Quake III Arena und John Carmak zugeschrieben, obwohl ich denke, dass es mehrere Iterationen dieser Funktion gab und sie ursprünglich nicht von ihm erstellt wurde ( ist Wikipedia nicht großartig? ).
quelle