Beeinflusst die modulare Programmierung die Rechenzeit?


19

Jeder sagt, ich sollte meinen Code modular gestalten, aber ist es nicht weniger effizient, wenn ich mehr Methodenaufrufe als weniger, aber größere Methoden verwende? Was ist der Unterschied in Java, C oder C ++?

Ich habe festgestellt, dass das Bearbeiten, Lesen und Verstehen einfacher ist, insbesondere in einer Gruppe. Ist der Rechenzeitverlust im Vergleich zu den Vorteilen für die Code-Ordnung unbedeutend?


2
Die Frage ist, wie lange es dauern wird, bis die von Ihnen ersparte Bearbeitungszeit die Zeit übersteigt, die Sie für schwierigere Wartungsarbeiten aufgewendet haben. Die Antwort darauf hängt ganz von Ihrer Anwendung ab.
Blrfl

2
Viele gute Fragen erzeugen einen gewissen Grad an Meinung, der auf Expertenerfahrung basiert. Die Antworten auf diese Frage basieren jedoch in der Regel fast ausschließlich auf Meinungen und nicht auf Fakten, Referenzen oder spezifischem Fachwissen.
gnat

10
Es ist auch erwähnenswert, dass der Rechenaufwand für einen Funktions- oder Methodenaufruf so gering ist, dass selbst in einem sehr großen Programm mit vielen Funktionen und Methodenaufrufen diese Aufrufe nicht einmal in der Tabelle aufgeführt werden.
Greyfade

1
@greyfade: Das gilt für die direkten Sprünge, aber ein zusätzlicher indirekter prognostizierter Sprung kann zum Beispiel ~ 3% der Gesamtlaufzeit des Programms kosten (nur eine Zahl aus dem Programm, das ich kürzlich überprüft habe - es war jedoch möglicherweise nicht repräsentativ). Abhängig von Ihrem Gebiet werden Sie es vielleicht für bedeutend halten oder nicht, aber es wurde in der Karte registriert (und es ist natürlich zumindest teilweise orthogonal zur Modularität).
Maciej Piechotka

4
Vorzeitige Optimierung ist die Wurzel allen Übels. Linearer Code ist etwas schneller als modularer Code. Modularer Code ist viel schneller als Spaghetti-Code. Wenn Sie linearen Code ohne ein (SEHR) gründliches Projekt des Ganzen anstreben, werden Sie am Ende Spaghetti-Code haben, das garantiere ich.
SF.

Antworten:


46

Ja, das ist irrelevant.

Computer sind unermüdliche, nahezu perfekte Ausführungsmotoren, die mit einer Geschwindigkeit arbeiten, die mit dem Gehirn nicht zu vergleichen ist. Während ein Funktionsaufruf die Ausführungszeit eines Programms messbar verlängert, entspricht dies nicht der zusätzlichen Zeit, die das Gehirn der nächsten am Code beteiligten Person benötigt, um die unlesbare Routine zu entwirren sogar zu beginnen zu verstehen , wie mit ihm zu arbeiten. Sie können die Berechnung als Scherz versuchen - nehmen Sie an, dass Ihr Code nur einmal gepflegt werden muss und dass dies nur eine halbe Stunde länger dauert, bis sich jemand mit dem Code vertraut gemacht hat. Nehmen Sie Ihre Prozessortaktfrequenz und berechnen: wie oft würde der Code laufen müssen , um noch zu träumen , dass der Ausgleich?

Kurz gesagt, Mitleid mit der CPU ist in 99,99% der Fälle völlig falsch. Verwenden Sie in den seltenen verbleibenden Fällen Profiler. Gehen Sie nicht davon aus, dass Sie diese Fälle erkennen können - das können Sie nicht.


2
Obwohl ich der Meinung bin, dass es sich meistens um eine vorzeitige Optimierung handelt, halte ich Ihr Argument mit der Zeit für schlecht. Die Zeit ist in verschiedenen Situationen relativ und Sie können nicht einfach so rechnen, wie Sie es getan haben.
Honza Brabec

28
+1 nur für den Ausdruck "Mitleid mit der CPU haben", weil er die Unachtsamkeit bei der vorzeitigen Optimierung so schön betont.
Michael Borgwardt

4
In hohem Maße verwandt: Wie schnell sind Computer im Alltag? Sich aus dem Weg zu gehen, um ein paar Funktionsaufrufe für "Geschwindigkeit" zu vermeiden, ist wie sich aus dem Weg zu gehen, um jemandem im Laufe seines gesamten Lebens insgesamt eine Minute zu sparen . Kurz gesagt: Es lohnt sich nicht einmal, darüber nachzudenken.
BlueRaja - Danny Pflughoeft

7
+1 für so eloquentes Verkaufen dessen, was viele vermissen, aber Sie haben vergessen, das wichtigste Gewicht zu dem Gleichgewicht hinzuzufügen, das das Mitleid mit der CPU noch schwerwiegender macht: Ignorieren Sie das verlorene Geld und die Zeit, die Sie für diese einmalige Wartung aufgewendet haben; Wenn es 1 Sekunde gedauert hat, gibt es immer noch ein weitaus heimtückischeres Waschbecken. Fehlerrisiko . Je verwirrender und schwieriger es für diesen späteren Betreuer ist, Ihren Code zu ändern, desto größer ist das Risiko, dass er Fehler darin implementiert, was unüberwindlich hohe unvermeidliche Kosten für die Benutzer verursachen kann und dass ein Fehler eine nachfolgende Wartung verursacht (was dazu führen kann) Bugs ...)
Jimmy Hoffa

Ein alter Lehrer von mir sagte immer: "Wenn Sie überlegen, ob Sie vorzeitig optimieren oder nicht, hören Sie gleich hier auf. Wenn dies die richtige Wahl wäre, würden Sie es wissen."
Ven

22

Es hängt davon ab, ob.

In der gletscherschwachen Welt der Web-Programmierung, in der alles mit menschlicher Geschwindigkeit abläuft, spielt methodenintensives Programmieren, bei dem die Kosten des Methodenaufrufs mit den Kosten der von der Methode durchgeführten Verarbeitung vergleichbar sind oder diese übersteigen, wahrscheinlich keine Rolle .

In der Welt der eingebetteten Systemprogrammierung und Interrupt-Handler für Interrupts mit hohen Raten spielt dies mit Sicherheit eine Rolle. In dieser Umgebung sind die üblichen Modelle "Speicherzugriff ist billig" und "Prozessor ist unendlich schnell" ausgefallen. Ich habe gesehen, was passiert, wenn ein objektorientierter Mainframe-Programmierer seinen ersten Interrupt-Handler mit hoher Rate schreibt. Es war nicht hübsch.

Vor einigen Jahren habe ich nicht-rekursives 8-Wege-Konnektivitäts-Blob-Coloring mit Echtzeit-FLIR-Bildern auf einem zu dieser Zeit anständigen Prozessor durchgeführt. Der erste Versuch verwendete einen Unterprogrammaufruf, und der Unterprogrammaufruf-Overhead verschlang den Prozessor am Leben. (4 Aufrufe PRO PIXEL x 64K Pixel pro Bild x 30 Bilder pro Sekunde = Sie finden es heraus). Beim zweiten Versuch wurde die Unterroutine in ein C-Makro geändert, ohne dass die Lesbarkeit darunter litt, und alles bestand aus Rosen.

Sie müssen genau hinschauen, was Sie tun und in welchem ​​Umfeld Sie es tun werden.


Denken Sie daran, dass ein Großteil der modernen Programmierprobleme am besten mit vielen objektorientierten, methodengerechten Codes zu tun hat. Sie wissen, wann Sie sich in einer Embedded-System-Umgebung befinden.
Kevin - Reinstate Monica

4
+1, aber mit einer Einschränkung: Es ist normalerweise der Fall, dass ein optimierender Compiler Inlining, Loop-Unrolling und Skalar- und Vektoroptimierungen häufig besser ausführt als eine Person. Der Programmierer muss noch die Sprache, den Compiler und die Maschine sehr gut kennen, um diese Vorteile nutzen zu können. Es ist also keine Zauberei.
Detly

2
Das stimmt, aber Sie haben Ihren Code optimiert, nachdem Sie die Logik geschrieben und ein Profil erstellt haben, um den problematischen Teil in Ihrem Algorithmus zu finden. Sie haben nicht aus Gründen der Leistung, sondern aus Gründen der Lesbarkeit mit dem Code begonnen. Makros helfen Ihnen dabei, Ihren Code lesbar zu halten.
Uwe Plonus

2
Beginnen Sie auch bei der Programmierung eingebetteter Systeme damit, dass Sie klaren, korrekten Code schreiben und optimieren, falls erforderlich und gemäß der Profilerstellung . Andernfalls ist es zu einfach, eine Menge Arbeit zu investieren, die sich nicht wirklich auf die Leistung / Codegröße auswirkt und die Quelle nur schwer verständlich macht.
Donal Fellows

1
@detly: Ja und nein. Moderne Compiler können IM ALLGEMEINEN IM RAHMEN DER SPRACHE bessere Arbeit leisten. Der menschliche Programmierer weiß, ob die durch die Sprache auferlegten Grenzen im konkreten Fall anwendbar sind. Zum Beispiel werden Cn- und C ++ - Compiler von den Sprachstandards benötigt, um es dem Programmierer zu ermöglichen, bestimmte sehr pathologische Dinge zu tun und Code zu generieren, der trotzdem funktioniert. Der Programmierer weiß möglicherweise, dass er nicht verrückt oder dumm oder abenteuerlustig genug ist, um diese Dinge zu tun, und nimmt Optimierungen vor, die ansonsten unsicher wären, weil er weiß, dass sie in diesem Fall sicher sind.
John R. Strohm

11

Zuallererst: Programme in einer höheren Sprache sollen von Menschen gelesen werden, nicht von Maschinen.

Schreiben Sie die Programme so, dass Sie sie verstehen. Denken Sie nicht an die Leistung (wenn Sie ernsthafte Leistungsprobleme haben, profilieren Sie Ihre Anwendung und verbessern Sie die Leistung dort, wo sie benötigt wird).

Selbst wenn es wahr ist, dass das Aufrufen einer Methode oder Funktion einen gewissen Aufwand erfordert, spielt dies keine Rolle. Heutzutage sollten Compiler in der Lage sein, Ihren Code in eine effiziente Maschinensprache zu kompilieren, damit der generierte Code für die Zielarchitektur effizient ist. Verwenden Sie die Optimierungsschalter Ihres Compilers, um den effizienten Code zu erhalten.


5
Ich würde sagen, wir sollten Programme so schreiben, dass andere sie verstehen.
Bartlomiej Lewandowski

Die erste Person, die ein Programm lesen muss, ist der Entwickler selbst. Das ist der Grund, warum ich dir geschrieben habe . Wenn andere Personen das Programm auch lesen können, ist es nett, aber (in meinen Augen) nicht notwendig (an erster Stelle). Wenn Sie in einem Team arbeiten, sollten auch andere Personen das Programm verstehen, aber meine Absicht war, dass diese Person ein Programm lesen muss, nicht Computer.
Uwe Plonus

5

Wenn Sie ansonsten eine große Funktion hätten und diese in viele kleinere aufteilen , werden diese kleineren inline dargestellt, da in diesem Fall der einzige Nachteil des Inlinings (zu viele Wiederholungen derselben Anweisungen) nicht relevant ist. Das heißt, Ihr Code verhält sich so, als hätten Sie eine große Funktion geschrieben.

Wenn sie aus irgendeinem Grund nicht inline sind und dies zu einem Leistungsproblem wird, sollten Sie manuelles Inlining in Betracht ziehen. Nicht alle Anwendungen sind vernetzte CRUD-Formulare mit enormen Latenzen.


2

Es fallen wahrscheinlich keine Berechnungskosten an. In der Regel beschäftigen sich die Compiler / JITs der letzten 10-20 Jahre mit der Funktion Inlining einwandfrei. Für C / C ++ ist es normalerweise auf "inlinierbare" Funktionen beschränkt (dh die Definition der Funktion steht dem Compiler während der Kompilierung zur Verfügung - das heißt, sie befindet sich im Header derselben Datei), aber die aktuellen Techniken von LTO überwinden dies.

Ob Sie Zeit für die Optimierung aufwenden sollten, hängt von dem Bereich ab, an dem Sie arbeiten. Wenn Sie mit „normalen“ Anwendungen arbeiten, die die meiste Zeit auf Eingaben gewartet haben, sollten Sie sich wahrscheinlich keine Gedanken über Optimierungen machen, es sei denn, die Anwendung fühlt sich langsam an.

Auch in solchen Fällen sollten Sie sich vor der Mikrooptimierung auf viele Dinge konzentrieren:

  • Wo liegen die Probleme? Normalerweise fällt es den Leuten schwer, die Hotspots zu finden, da wir den Quellcode anders lesen. Wir haben ein unterschiedliches Verhältnis der Betriebszeit und führen sie nacheinander durch, während moderne Prozessoren dies nicht tun .
  • Muss jedes Mal eine Berechnung durchgeführt werden? Wenn Sie beispielsweise einen einzelnen Parameter von Tausenden ändern, möchten Sie möglicherweise nur einen betroffenen Teil anstelle des gesamten Modells berechnen.
  • Verwenden Sie einen optimalen Algorithmus? Der Wechsel von O(n)zu O(log n)könnte viel größere Auswirkungen haben als alles, was Sie durch Mikrooptimierung erreichen könnten.
  • Verwenden Sie geeignete Strukturen? Angenommen, Sie verwenden ein, Listwenn Sie es benötigen, HashSetdamit Sie O(n)nachschlagen können, wann Sie es haben könnten O(1).
  • Nutzen Sie Parallelität effizient? Gegenwärtig können sogar Mobiltelefone 4 oder mehr Kerne haben. Möglicherweise ist es verlockend, Threads zu verwenden. Sie sind jedoch keine Wunderwaffe, da sie Synchronisationskosten verursachen (ganz zu schweigen davon, dass sie keinen Sinn ergeben, wenn das Problem speichergebunden ist).

Selbst wenn Sie entscheiden, dass Sie eine Mikrooptimierung durchführen müssen (was praktisch bedeutet, dass Ihre Software in HPC verwendet wird, eingebettet ist oder nur von einer sehr großen Anzahl von Personen verwendet wird - andernfalls überwiegen die zusätzlichen Wartungskosten die Computerzeitkosten), die Sie benötigen um die Hotspots (Kernel) zu identifizieren, die Sie beschleunigen möchten. Aber dann solltest du wahrscheinlich:

  1. Kennen Sie genau die Plattform, auf der Sie arbeiten
  2. Kennen Sie genau den Compiler, an dem Sie arbeiten, und wissen Sie, welche Optimierung möglich ist, und wie Sie idiomatischen Code schreiben, um diese Optimierung zu ermöglichen
  3. Denken Sie über Speicherzugriffsmuster nach und wie viel können Sie in den Cache passen (genaue Größe, die Sie aus Punkt 1 kennen).
  4. Wenn Sie rechnergebunden sind, überlegen Sie, ob Sie die Berechnung neu organisieren möchten, um die Berechnung zu speichern

Als letzte Bemerkung. Das einzige Problem, das Sie bei Methodenaufrufen haben, sind normalerweise die indirekten Sprünge (virtuelle Methoden), die vom Verzweigungsprädiktor nicht vorhergesagt wurden (leider ist der indirekte Sprung der schwierige Fall dafür). Jedoch:

  • Java hat eine JIT, die in vielen Fällen die Art der Klasse und damit das Sprungziel vorhersagen kann. In Hotspots sollten Sie daher nicht viele Probleme haben.
  • C ++ - Compiler führen häufig eine Programmanalyse durch und können zumindest in einigen Fällen das Ziel zur Kompilierungszeit vorhersagen.
  • In beiden Fällen sollte das Inlining funktionieren, wenn das Ziel vorhergesagt wurde. Wenn der Compiler die Inlining-Chancen nicht erfüllen könnte, könnten wir es auch nicht.

0

Meine Antwort wird wahrscheinlich nicht zu sehr auf die vorhandenen Antworten eingehen, aber ich bin der Meinung, dass meine zwei Cent hilfreich sein könnten.

Zuerst; ja, aus Gründen der Modularität verzichten Sie normalerweise auf eine gewisse Ausführungszeit. Wenn Sie alles in Assembler-Code schreiben, erhalten Sie die beste Geschwindigkeit. Das gesagt...

Kennst du YouTube? Wahrscheinlich der Standort mit der höchsten Bandbreite oder der zweitgrößte nach Netflix? Sie schreiben einen großen Teil ihres Codes in Python, einer hochmodularen Sprache, die nicht ganz für erstklassige Leistung ausgelegt ist.

Die Sache ist, wenn etwas schief geht und sich Benutzer über das langsame Laden von Videos beschweren, gibt es nicht viele Szenarien, in denen diese Langsamkeit letztendlich auf die langsame Ausführungsgeschwindigkeit von Python zurückzuführen ist. Die schnelle Neukompilierung von Python und die modulare Möglichkeit, neue Dinge ohne Typprüfung auszuprobieren, werden es den Ingenieuren jedoch wahrscheinlich ermöglichen, das Problem schnell zu beheben ("Wow. Unser neuer Praktikant hat eine Schleife geschrieben, die eine neue SQL-Unterabfrage ausführt für JEDES Ergebnis. ") oder (" Oh, Firefox hat dieses alte Caching-Header-Format verworfen und eine Python-Bibliothek erstellt, um das neue einfach einzurichten ")

In diesem Sinne kann eine modulare Sprache auch in Bezug auf die Ausführungszeit als schneller angesehen werden, da es wahrscheinlich einfacher sein wird, den Code neu zu organisieren, damit er optimal funktioniert, sobald Sie die Engpässe ermittelt haben. So viele Ingenieure werden Ihnen sagen, dass die schweren Performance-Hits nicht dort waren, wo sie gedacht hatten (und tatsächlich wurden die Dinge, die sie optimiert haben, kaum benötigt oder funktionierten nicht einmal so, wie sie es erwartet hatten!).


0

Ja und nein. Wie andere Programme bereits angemerkt haben, zuerst auf Lesbarkeit, dann auf Effizienz. Es gibt jedoch Standardverfahren, die sowohl lesbar als auch effizient sind. Der meiste Code wird eher selten ausgeführt, und Sie werden von der Optimierung sowieso nicht viel profitieren.

Java kann kleinere Funktionsaufrufe einbinden, so dass es kaum einen Grund gibt, das Schreiben der Funktionen zu vermeiden. Optimierer arbeiten in der Regel besser mit einfacher zu lesendem Code. Es gibt Studien, die Verknüpfungen aufzeigen, die theoretisch schneller ablaufen sollten, tatsächlich aber länger dauern. Der JIT-Compiler funktioniert wahrscheinlich besser, der Code ist kleiner und häufig ausgeführte Teile können identifiziert und optimiert werden. Ich habe es nicht ausprobiert, aber ich würde erwarten, dass eine große Funktion, die relativ selten aufgerufen wird, nicht kompiliert wird.

Dies trifft wahrscheinlich nicht auf Java zu, aber eine Studie ergab, dass größere Funktionen tatsächlich langsamer ausgeführt werden, da ein anderes Speicherreferenzmodell erforderlich ist. Dies war Hardware- und Optimierungsspezifisch. Für kleinere Module wurden Anweisungen verwendet, die innerhalb einer Speicherseite arbeiteten. Diese waren schneller und kleiner als die Anweisungen, die erforderlich waren, wenn die Funktion nicht zu einer Seite passte.

Es gibt Fälle, in denen es sich lohnt, den Code zu optimieren, aber im Allgemeinen müssen Sie den Code profilieren, um festzustellen, wo er sich befindet. Ich finde, es ist oft nicht der Code, den ich erwartet habe.

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.