Einkapselung schadet der Leistung

Ich weiß, diese Frage ist irgendwie dumm, vielleicht ist es nur ein Teil des Schreibens von Code, aber es scheint definieren einfache Funktionen kann wirklich schaden Leistung stark … Ich habe versucht, diese einfache Test:

def make_legal_foo_string(x): return "This is a foo string: " + str(x) def sum_up_to(x): return x*(x+1)/2 def foo(x): return [make_legal_foo_string(x),sum_up_to(x),x+1] def bar(x): return ''.join([str(foo(x))," -- bar !! "]) 

Es ist ein sehr guter Stil und macht Code klar, aber es kann dreimal so langsam sein, wie einfach nur schreiben sie buchstäblich. Es ist unausweichlich für Funktionen, die Nebenwirkungen haben können, aber es ist eigentlich fast trivial, einige Funktionen zu definieren, die nur buchstäblich durch Codezeilen ersetzt werden sollen, jedes Mal, wenn sie erscheinen, den Quellcode in das übersetzen und nur dann kompilieren. Dass ich für magische Zahlen denke, es dauert nicht viel Zeit, aus dem Gedächtnis zu lesen, aber wenn sie nicht geändert werden sollen, warum dann nicht einfach jede Instanz von 'Magie' mit einem Literal ersetzen, bevor der Code kompiliert?

8 Solutions collect form web for “Einkapselung schadet der Leistung”

Funktionsaufruf Gemeinkosten sind nicht groß; Sie werden sie normalerweise nicht bemerken. Sie sehen sie nur in diesem Fall, weil Ihr aktueller Code (x * x) selbst so völlig trivial ist. In jedem echten Programm, das echte Arbeit macht, wird die Menge an Zeit, die in Funktionsaufruf Overhead verbracht wird, vernachlässigbar klein.

(Nicht das würde ich wirklich empfehlen, Foo, Identität und Platz im Beispiel, auf jeden Fall, sie sind so trivial es ist genauso lesbar, sie inline zu haben und sie nicht wirklich verkapseln oder abstrakt etwas.)

Wenn sie nicht geändert werden sollen, warum dann nicht nur jede Instanz von 'Magie' mit einem Literal ersetzen, bevor der Code kompiliert?

Weil Programme geschrieben werden, um für Sie leicht zu lesen und zu pflegen. Sie könnten Konstanten mit ihren wörtlichen Werten ersetzen, aber es würde das Programm schwerer zu arbeiten, für einen Nutzen, der so klein ist, werden Sie wahrscheinlich niemals sogar in der Lage sein, es zu messen: die Höhe der vorzeitigen Optimierung .

Einkapselung ist etwa eins und nur eins: Lesbarkeit. Wenn Sie wirklich so besorgt über die Leistung sind, dass Sie bereit sind, das Strippen der eingekapselten Logik zu starten, können Sie auch nur die Codierung in der Montage beginnen.

Die Kapselung hilft auch beim Debuggen und beim Hinzufügen. Betrachten Sie die folgenden: Lasst uns sagen, Sie haben ein einfaches Spiel und müssen Code hinzufügen, dass die Spieler Gesundheit unter einigen Umständen erschöpft. Einfach, ja

 def DamagePlayer(dmg): player.health -= dmg; 

Das ist sehr trivialer Code, also ist es sehr verlockend, einfach "player.health – =" überall zu streuen. Aber was, wenn du später einen Powerup hinzufügen willst, der den Spieler beendet hat, während er aktiv ist? Wenn die Logik noch eingekapselt ist, ist es einfach:

 def DamagePlayer(dmg): if player.hasCoolPowerUp: player.health -= dmg / 2 else player.health -= dmg 

Nun, betrachten Sie, wenn Sie vernachlässigt haben, um dieses Bit der Logik zu umschließen, weil es Einfachheit ist. Jetzt sind Sie auf der Suche nach der gleichen Logik in 50 verschiedenen Orten, mindestens einer von denen Sie fast sicher zu vergessen, was führt zu seltsamen Bugs wie: "Wenn Spieler hat Powerup alle Schäden ist halbiert, außer wenn von AlienSheep Feinde getroffen. .. "

Möchtest du Probleme mit Alien Sheep haben? Das glaube ich nicht 🙂

In aller Ernsthaftigkeit, der Punkt, den ich versuche zu machen ist, ist die Verkapselung eine sehr gute Sache in den richtigen Umständen. Natürlich ist es auch leicht zu überkapseln, was genauso problematisch sein kann. Auch gibt es Situationen, in denen die Geschwindigkeit wirklich wirklich wichtig ist (obwohl sie selten sind), und dass extra wenige Taktzyklen es wert ist. Über die einzige Möglichkeit, die richtige Balance zu finden, ist die Praxis. Schütteln Sie die Kapselung nicht, weil es langsamer ist. Die Vorteile in der Regel weit überwiegen die Kosten.

Ich weiß nicht, wie gut Python-Compiler sind, aber die Antwort auf diese Frage für viele Sprachen ist, dass der Compiler die Anrufe zu kleinen Prozeduren / Funktionen / Methoden optimiert, indem man sie inliniert. In der Tat, in einigen Sprach-Implementierungen erhalten Sie in der Regel bessere Leistung durch NICHT versuchen, "Mikro-optimieren" den Code selbst.

Was du redest, ist die Wirkung von Inlining-Funktionen zur Effizienzsteigerung.

Es ist sicherlich wahr in deinem Python-Beispiel, dass die Kapselung die Leistung verletzt. Aber es gibt etwas Gegenbeispiel dazu:

  1. In Java, die Definition von Getter & Setter statt der Definition von öffentlichen Member-Variablen führt nicht zu Performance-Verschlechterung als JIT inline die Getter & Setter.

  2. Manchmal ruft eine Funktion wiederholt auf, kann besser sein als das Inlining durchzuführen, da der ausgeführte Code dann in den Cache passen kann. Inlining kann Code-Explosion verursachen …

Herauszufinden, was in eine Funktion zu machen und was nur Inline enthalten ist, ist etwas von einer Kunst. Viele Faktoren (Leistung, Lesbarkeit, Wartbarkeit) fließen in die Gleichung ein.

Ich finde dein Beispiel in vielerlei Hinsicht dumm – eine Funktion, die gerade das Argument zurückgibt? Es sei denn, es ist eine Überlastung, die die Regeln ändert, ist es dumm. Eine Funktion, um Dinge zu quadrieren? Wieder, warum stören Ihre Funktion 'foo' sollte wohl einen String zurückgeben, damit er direkt verwendet werden kann:

 ''.join(foo(x)," -- bar !! "]) 

Das ist wohl ein korrekteres Einkapselungsniveau in diesem Beispiel.

Wie ich schon sagte, hängt es wirklich von den Umständen ab. Leider ist das eine Art von Dingen, die sich nicht gut für Beispiele eignet.

IMO, dies bezieht sich auf Funktionsaufrufkosten . Welche sind meist vernachlässigbar, aber nicht null. Das Aufteilen des Codes in einer Menge von sehr kleinen Funktionen kann verletzt werden. Besonders in interpretierten Sprachen, wo keine vollständige Optimierung verfügbar ist.

Inlining-Funktionen können die Leistung verbessern, aber es kann sich auch verschlechtern. Siehe z. B. C ++ FQA Lite für Erklärungen ("Inlining kann Code schneller durch Beseitigung von Funktionsaufruf Overhead, oder langsamer durch die Erzeugung zu viel Code, verursacht Anweisung Cache misses"). Dies ist nicht C ++ spezifisch. Besser lassen Optimierungen für Compiler / Dolmetscher, wenn sie nicht wirklich notwendig sind .

Übrigens sehe ich keinen großen Unterschied zwischen zwei Versionen:

 $ python bench.py fine-grained function decomposition: 5.46632194519 one-liner: 4.46827578545 $ python --version Python 2.5.2 

Ich denke, dieses Ergebnis ist akzeptabel. Sehen Sie bench.py ​​im Pastebin .

Es gibt einen Performance-Hit für die Verwendung von Funktionen, da gibt es den Overhead des Sprungs zu einer neuen Adresse, drückt Register auf dem Stapel und kehrt am Ende zurück. Dieser Overhead ist jedoch sehr klein, und selbst in peformance kritischen Systemen, die sich um solchen Overhead sorgen, ist höchstwahrscheinlich eine vorzeitige Optimierung.

Viele Sprachen vermeiden diese Probleme in kleinen häufigen genannt Funktionen durch die Verwendung von Inlining, was im Wesentlichen das, was Sie oben tun.

Python macht keine Inlining. Das nächste, was Sie tun können, ist Makros, um die Funktionsaufrufe zu ersetzen.

Diese Art von Performance-Problem wird besser von einer anderen Sprache bedient, wenn Sie die Art von Geschwindigkeit, die durch Inlining (meist marginal, und manchmal detremental), dann müssen Sie nicht mit Python für was auch immer Sie arbeiten zu betrachten.

Es gibt einen guten technischen Grund, warum das, was Sie vorgeschlagen haben, unmöglich ist. In Python sind Funktionen, Konstanten und alles andere zur Laufzeit zugänglich und können bei Bedarf jederzeit geändert werden ; Sie könnten auch von externen Modulen gewechselt werden. Dies ist ein ausdrückliches Versprechen von Python und man würde einige äußerst wichtige Gründe brauchen, um es zu brechen.

Zum Beispiel, hier ist die gemeinsame logging idiom:

 # beginning of the file xxx.py log = lambda *x: None def something(): ... log(...) ... 

(Hier log nichts) und dann an einem anderen Modul oder an der interaktiven Eingabeaufforderung:

 import xxx xxx.log = print xxx.something() 

Wie Sie sehen, hier wird log durch ganz anderes Modul modifiziert — oder durch den Benutzer — so dass die Protokollierung jetzt funktioniert. Das wäre unmöglich, wenn das log optimiert wurde.

Ähnlich, wenn eine Ausnahme in make_legal_foo_string passieren make_legal_foo_string (dies ist möglich, zB wenn x.__str__() ist gebrochen und gibt None ) Sie würden mit einem Quell-Zitat aus einer falschen Linie und sogar vielleicht aus einer falschen Datei in Ihrem getroffen werden Szenario.

Es gibt einige Werkzeuge, die in der Tat einige Optimierungen auf Python-Code anwenden, aber ich denke nicht an die Art, die Sie vorgeschlagen haben.

  • Gibt es einen Weg zu umgehen Python list.append () immer schlanker in einer Schleife, wie die Liste wächst?
  • Appengine, Leistungsdegradation mit python27
  • Sparse_hash_map ist für bestimmte Daten sehr langsam
  • Str Performance in Python
  • Warum ist langweilig langsamer als für Schleife
  • Verbessern Sie die Funktion der Funktion ohne Parallelisierung
  • Schnellste Weg, um Werte in großen, knalligen Arrays zu vergleichen?
  • Interpolation von Daten aus einer Nachschlagetabelle
  • Ist es eine schlechte Praxis, Pythons getattr ausgiebig zu benutzen?
  • Warum ist variable1 + = variable2 viel schneller als variable1 = variable1 + variable2?
  • Vermeiden Sie eine deepcopy bei einem BFS
  • Python ist die beste Programmiersprache der Welt.