Hat sich die schrittweise Änderung der Codeschreibmethodik auf die Systemleistung ausgewirkt? Und sollte es mich interessieren?


35

TD; DR:

Es gab einige Verwirrung darüber, was ich fragte, also hier ist die treibende Idee hinter der Frage:

Ich wollte immer die Frage sein, was es ist. Ich könnte es ursprünglich nicht gut artikuliert haben. Aber die Absicht war schon immer " modularer, getrennter, lose gekoppelter, entkoppelter, umgestalteter Code ", der von Natur aus deutlich langsamer ist als " monolithische Einzeleinheit, alles an einem Ort, eine Datei, eng gekoppelter Code". Der Rest sind nur Details und verschiedene Manifestationen davon, die mir damals oder heute oder später begegnet sind. In gewisser Hinsicht ist es sicher langsamer. Wie bei einer nicht defragmentierten Festplatte müssen Sie die Teile von überall aufheben. Es ist langsamer Sicher. Aber sollte es mich interessieren?

Und die Frage ist nicht über ...

Es geht nicht um Mikrooptimierung, vorzeitige Optimierung usw. Es geht nicht um "diesen oder jenen Teil zu Tode optimieren".

Was ist es dann?

Es geht um die allgemeine Methodik und die Techniken und Denkweisen beim Schreiben von Code, die im Laufe der Zeit entstanden sind:

  • "Fügen Sie diesen Code als Abhängigkeit in Ihre Klasse ein"
  • "schreibe eine Datei pro Klasse"
  • msgstr "trennen Sie Ihre Ansicht von Ihrer Datenbank, Ihrem Controller, Ihrer Domain".
  • Schreiben Sie keine spaghetti-homogenen einzelnen Codeblöcke, sondern schreiben Sie viele separate modulare Komponenten, die zusammenarbeiten

Es geht um den Weg und den Stil des Codes, der derzeit - innerhalb dieses Jahrzehnts - in den meisten Frameworks gesehen und befürwortet wird, befürwortet auf Konventionen, die über die Community weitergegeben werden. Es ist eine Verlagerung des Denkens von "monolithischen Blöcken" zu "Mikrodienstleistungen". Und damit einher geht der Preis in Bezug auf Leistung und Overhead auf Maschinenebene und einige Overheads auf Programmiererebene.

Die ursprüngliche Frage lautet:

Im Bereich der Informatik habe ich eine bemerkenswerte Veränderung im Denken in Bezug auf die Programmierung festgestellt. Ich stoße ziemlich oft auf den Rat, der so lautet:

  • Schreiben Sie kleineren funktionsbezogenen Code (auf diese Weise überprüfbarer und wartbarer).
  • Zerlegen Sie vorhandenen Code in immer kleinere Codestücke, bis die meisten Ihrer Methoden / Funktionen nur noch wenige Zeilen lang sind und klar ist, wozu sie dienen (wodurch im Vergleich zu einem größeren monolithischen Block mehr Funktionen erstellt werden).
  • Schreibe Funktionen, die nur eines tun - Trennung von Anliegen usw. (was normalerweise mehr Funktionen und mehr Frames auf einem Stapel erzeugt)
  • mehr Dateien erstellen (eine Klasse pro Datei, mehr Klassen für Dekompositionszwecke, für Layer-Zwecke wie MVC, Domänenarchitektur, Entwurfsmuster, OO usw., wodurch mehr Dateisystemaufrufe erstellt werden)

Dies ist eine Änderung im Vergleich zu den "alten" oder "veralteten" oder "Spaghetti" Codierungsmethoden, bei denen Sie Methoden haben, die sich über 2500 Zeilen erstrecken, und große Klassen und Gottobjekte, die alles tun.

Meine Frage lautet:

Wenn es um Maschinencode geht, um Einsen und Nullen, um Montageanweisungen, um Festplattenplatten, sollte ich mir überhaupt Sorgen machen, dass mein perfekt klassengetrennter OO-Code mit einer Vielzahl überarbeiteter Funktionen und Methoden auch generiert wird viel mehr Aufwand?

Einzelheiten

Obwohl ich nicht genau weiß, wie OO-Code und seine Methodenaufrufe letztendlich in ASM verarbeitet werden und wie DB-Aufrufe und Compiler-Aufrufe dazu führen, dass sich der Aktuatorarm auf einem HDD-Plattenteller bewegt, habe ich eine Idee. Ich gehe davon aus, dass jeder zusätzliche Funktionsaufruf, Objektaufruf oder "#include" -Aufruf (in einigen Sprachen) einen zusätzlichen Befehlssatz generiert, wodurch die Codevolumen erhöht und verschiedene "Code-Verkabelungs" -Overheads hinzugefügt werden , ohne tatsächlichen "nützlichen" Code hinzuzufügen . Ich stelle mir auch vor, dass gute Optimierungen an ASM vorgenommen werden können, bevor es tatsächlich auf der Hardware ausgeführt wird, aber diese Optimierung kann nur so viel bewirken.

Daher meine Frage: Wie viel Overhead (in Bezug auf Speicherplatz und Geschwindigkeit) bewirkt gut separierter Code (Code, der auf Hunderte von Dateien, Klassen und Entwurfsmustern usw. aufgeteilt ist), verglichen mit einer einzigen großen Methode, die Code enthält? alles in einer monolithischen Akte ", aufgrund dieses Overheads?

UPDATE zur Verdeutlichung:

Ich gehe davon aus, dass das Aufteilen und Umgestalten desselben Codes , das Entkoppeln desselben in immer mehr Funktionen und Objekte sowie Methoden und Klassen dazu führen wird, dass immer mehr Parameter zwischen kleineren Codeteilen übertragen werden. Refactoring-Code muss den Thread auf jeden Fall am Laufen halten, und dazu müssen Parameter übergeben werden. Weitere Methoden oder mehr Klassen oder mehr Fabrikmethoden Entwurfsmuster, führt zu mehr Overhead von verschiedenen Bits von Informationen vorbei mehr , als es der Fall in einer einzigen monolithischen Klasse oder Methode.

Irgendwo hieß es (Zitat TBD), dass bis zu 70% des gesamten Codes aus dem MOV-Befehl von ASM besteht - das Laden von CPU-Registern mit richtigen Variablen, nicht die eigentliche Berechnung. In meinem Fall laden Sie die CPU-Zeit mit PUSH / POP-Anweisungen, um die Verknüpfung und Parameterübergabe zwischen verschiedenen Codeteilen bereitzustellen. Je kleiner Sie Ihre Codeteile machen, desto mehr Overhead "Verknüpfung" ist erforderlich. Ich bin besorgt darüber, dass diese Verknüpfung zu einer Aufblähung und Verlangsamung der Software beiträgt, und ich frage mich, ob ich darüber besorgt sein sollte und wie viel, wenn überhaupt, weil aktuelle und zukünftige Generationen von Programmierern Software für das nächste Jahrhundert entwickeln müssen mit Software leben und diese konsumieren, die mit diesen Methoden erstellt wurde.

UPDATE: Mehrere Dateien

Ich schreibe jetzt neuen Code, der langsam alten Code ersetzt. Insbesondere habe ich festgestellt, dass eine der alten Klassen eine ~ 3000-Zeilen-Datei war (wie bereits erwähnt). Jetzt sind es 15-20 Dateien, die sich in verschiedenen Verzeichnissen befinden, einschließlich Testdateien und ohne PHP-Framework, das ich verwende, um einige Dinge zusammenzubinden. Weitere Dateien werden ebenfalls kommen. Wenn es um Festplatten-E / A geht, ist das Laden mehrerer Dateien langsamer als das Laden einer großen Datei. Natürlich werden nicht alle Dateien geladen, sie werden nach Bedarf geladen, und es gibt Optionen für das Zwischenspeichern von Datenträgern und für das Zwischenspeichern von Speicher, und dennoch glaube ich, dass dies loading multiple filesmehr Verarbeitung erfordert als loading a single fileSpeicher. Ich füge das meiner Sorge hinzu.

UPDATE: Abhängigkeit Alles einschleusen

Ich komme nach einer Weile darauf zurück. Ich glaube, meine Frage wurde missverstanden. Oder vielleicht habe ich einige Antworten falsch verstanden. Ich spreche nicht von Mikrooptimierung, da einige Antworten herausgegriffen haben (zumindest denke ich, dass es eine Fehlbezeichnung ist, wenn ich von Mikrooptimierung spreche), sondern von der Bewegung des "Refactor-Codes zum Lösen der engen Kopplung" als Ganzes auf jeder Ebene des Codes. Ich bin erst kürzlich von Zend Con gekommen, wo diese Art von Code einer der Kernpunkte und Mittelpunkte der Konvention war. Entkoppeln Sie die Logik von der Ansicht, die Ansicht vom Modell und das Modell von der Datenbank. Wenn möglich, entkoppeln Sie die Daten von der Datenbank. Abhängigkeit-Injizieren Sie alles, was manchmal nur das Hinzufügen von Verdrahtungscode (Funktionen, Klassen, Boilerplate) bedeutet, der nichts bewirkt, dient aber als Naht- / Hakenpunkt und verdoppelt in den meisten Fällen problemlos die Codegröße.

UPDATE 2: Beeinträchtigt "Aufteilen von Code in mehrere Dateien" die Leistung erheblich (auf allen Rechenebenen)?

Wie wirkt sich die Philosophie des compartmentalize your code into multiple filesComputing von heute aus (Leistung, Festplattennutzung, Speicherverwaltung, CPU-Verarbeitungsaufgaben)?

Ich rede über

Vor...

In einer hypothetischen und doch realistischen, nicht allzu fernen Vergangenheit könnte man leicht einen Monoblock einer Datei schreiben, die Modell- und Ansichts- und Controller-Spaghetti oder nicht-Spaghetti-codierte Dateien enthält, die jedoch alles ausführen, sobald sie bereits geladen sind. Als ich in der Vergangenheit einige Benchmarks mit C-Code durchführte, stellte ich fest, dass es VIEL schneller ist, eine einzelne 900-MB-Datei in den Speicher zu laden und in großen Blöcken zu verarbeiten, als eine Reihe kleinerer Dateien zu laden und sie in einem kleineren Friedensmahl zu verarbeiten Brocken, die am Ende die gleiche Arbeit machen.

.. Und nun*

Heute schaue ich mir Code an, der ein Hauptbuch anzeigt, das Funktionen wie ... aufweist. Wenn ein Artikel eine "Bestellung" ist, zeige ich den HTML-Block der Bestellung an. Wenn eine Werbebuchung kopiert werden kann, drucken Sie einen HTML-Block, der ein Symbol und HTML-Parameter dahinter anzeigt, damit Sie die Kopie erstellen können. Wenn das Element nach oben oder unten verschoben werden kann, zeigen Sie die entsprechenden HTML-Pfeile an. Ich kann durch Zend Framework erstellenpartial()calls, was im Wesentlichen bedeutet "eine Funktion aufrufen, die Ihre Parameter aufnimmt und in eine separate HTML-Datei einfügt, die sie auch aufruft". Je nachdem, wie detailliert ich sein möchte, kann ich separate HTML-Funktionen für die kleinsten Teile des Hauptbuchs erstellen. Eine für Pfeil nach oben, Pfeil nach unten, eine für "Kann ich diesen Artikel kopieren?" Usw. Erstellen Sie mühelos mehrere Dateien, um nur einen kleinen Teil der Webseite anzuzeigen. Unter Berücksichtigung meines Codes und des Zend Framework-Codes hinter den Kulissen ruft das System / der Stack wahrscheinlich ungefähr 20-30 verschiedene Dateien auf.

Was?

Ich interessiere mich für Aspekte, die Abnutzung der Maschine, die durch Unterteilen von Code in viele kleinere separate Dateien entsteht.

Wenn Sie beispielsweise mehr Dateien laden, müssen Sie diese an verschiedenen Stellen des Dateisystems und an verschiedenen Stellen der physischen Festplatte ablegen. Dies bedeutet mehr Zeit für das Suchen und Lesen der Festplatte.

Für die CPU bedeutet dies wahrscheinlich mehr Kontextwechsel und Laden verschiedener Register.

In diesem Unterblock (Update Nr. 2) geht es mir genauer darum, wie sich die Verwendung mehrerer Dateien für dieselben Aufgaben, die in einer einzelnen Datei ausgeführt werden könnten, auf die Systemleistung auswirkt.

Verwenden der Zend Form API im Vergleich zu einfachem HTML

Ich habe die Zend Form API mit den neuesten und modernsten OO-Methoden verwendet, um ein HTML-Formular mit Validierung zu erstellen, das POSTin Domänenobjekte umgewandelt wird.

Ich brauchte 35 Dateien, um es zu machen.

35 files = 
    = 10 fieldsets x {programmatic fieldset + fieldset manager + view template} 
    + a few supporting files

All dies könnte durch ein paar einfache HTML- + PHP- + JS- + CSS-Dateien ersetzt werden, möglicherweise insgesamt 4 leichte Dateien.

Ist es besser? Lohnt es sich? Stellen Sie sich vor, Sie laden 35 Dateien und zahlreiche Zend Zramework-Bibliotheksdateien, mit denen sie funktionieren, im Vergleich zu 4 einfachen Dateien.


1
Ehrfürchtige Frage. Ich mache ein Benchmarking (nimm mir einen Tag Zeit, um ein paar gute Fälle zu finden). Geschwindigkeitsverbesserungen in diesem Ausmaß sind jedoch mit enormen Kosten für die Lesbarkeit und die Entwicklungskosten verbunden. Meine anfängliche Vermutung ist, dass das Ergebnis vernachlässigbare Leistungssteigerungen sind.
Dan Sabin

7
@Dan: Kannst du es bitte in deinen Kalender schreiben, um den Code nach 1, 5 und 10 Jahren Wartung zu vergleichen? Wenn ich mich erinnere,
schaue

1
Ja, das ist der wahre Kicker. Ich denke, wir sind uns alle einig, dass weniger Suchvorgänge und Funktionsaufrufe schneller sind. Ich kann mir jedoch keinen Fall vorstellen, in dem dies Dingen vorgezogen wurde, die es einfacher und leichter machen, neue Teammitglieder zu schulen.
Dan Sabin

2
Zur Verdeutlichung haben Sie spezielle Geschwindigkeitsanforderungen für Ihr Projekt. Oder möchten Sie einfach, dass Ihr Code "schneller geht!" Wenn es das letztere ist, würde ich mir darüber keine Sorgen machen, Code, der schnell genug, aber einfach zu warten ist, ist weitaus besser als Code, der schneller als schnell genug ist, aber ein Durcheinander ist.
Cormac Mulhall

4
Die Idee, Funktionen aus Performancegründen zu meiden, ist genau das, was Dijkstra in seinem berühmten Zitat über vorzeitige Optimierung kritisiert hat. Im Ernst, ich kann nicht
RibaldEddie

Antworten:


10

Meine Frage lautet: Wenn es darum geht, Maschinencode, Einsen und Nullen oder Montageanweisungen aufzurufen, sollte ich mir dann überhaupt Sorgen machen, dass mein klassengetrennter Code mit einer Vielzahl von kleinen bis kleinen Funktionen zu viel zusätzlichen Overhead erzeugt?

Meine Antwort lautet: Ja, das solltest du. Nicht, weil Sie viele kleine Funktionen haben (früher war der Aufwand für das Aufrufen von Funktionen einigermaßen hoch und Sie konnten Ihr Programm verlangsamen, indem Sie eine Million kleine Aufrufe in Schleifen ausführten. Heute werden sie von Compilern für Sie eingebunden und die verbleibenden Funktionen werden übernommen Sie sollten sich also keine Gedanken darüber machen, sondern das Konzept der Überlagerung in Ihre Programme einführen, wenn die Funktionalität zu klein ist, um sinnvoll im Kopf zu stecken. Wenn Sie über größere Komponenten verfügen, können Sie sich ziemlich sicher sein, dass diese nicht immer wieder dieselben Aufgaben ausführen. Sie können Ihr Programm jedoch so detailliert gestalten, dass Sie möglicherweise die Aufrufpfade nicht wirklich verstehen und am Ende etwas damit zu tun haben das funktioniert kaum (und ist kaum zu warten).

Ich habe zum Beispiel an einem Ort gearbeitet, der mir ein Referenzprojekt für einen Webdienst mit 1 Methode gezeigt hat. Das Projekt umfasste 32 .cs-Dateien - für einen einzigen Webdienst! Ich fand, dass dies viel zu viel Komplexität war, obwohl jedes Teil winzig und für sich leicht zu verstehen war, musste ich bei der Beschreibung des Gesamtsystems schnell Anrufe nachverfolgen, nur um zu sehen, was zum Teufel es tat (dort) waren auch zu viele Abstraktionen involviert, wie man erwarten würde). Mein Ersatz-Webservice bestand aus 4 .cs-Dateien.

Ich habe die Leistung nicht gemessen, da ich davon ausgehe, dass sie insgesamt in etwa gleich gewesen wäre, aber ich kann garantieren, dass meine Wartung erheblich günstiger war. Wenn alle davon sprechen, dass die Zeit des Programmierers wichtiger ist als die CPU-Zeit, dann erschaffen Sie komplexe Monster, die sowohl bei der Entwicklung als auch bei der Wartung viel Zeit für Programmierer kosten. Sie müssen sich fragen, ob sie Entschuldigungen für schlechtes Benehmen liefern.

Irgendwo hieß es (Zitat TBD), dass bis zu 70% des gesamten Codes aus dem MOV-Befehl von ASM besteht - das Laden von CPU-Registern mit richtigen Variablen, nicht die eigentliche Berechnung.

Dies ist jedoch die Aufgabe von CPUs, die Bits aus dem Speicher in Register verschieben, sie addieren oder subtrahieren und sie dann wieder in den Speicher verschieben. Alles Rechnen läuft darauf hinaus. Wohlgemerkt, ich hatte einmal ein Multithread-Programm, das die meiste Zeit mit dem Kontextwechsel (dh Speichern und Wiederherstellen des Registerzustands von Threads) als mit der Bearbeitung des Thread-Codes verbracht hat. Eine einfache Sperre an der falschen Stelle hat die Leistung wirklich verbessert, und es war auch so ein harmloser Code.

Mein Rat ist also: Finden Sie einen vernünftigen Mittelweg zwischen den beiden Extremen, der Ihren Code für andere Menschen gut aussehen lässt, und testen Sie das System, um festzustellen, ob es eine gute Leistung erbringt. Verwenden Sie die Betriebssystemfunktionen, um sicherzustellen, dass sie mit CPU, Speicher, Datenträger und Netzwerk-E / A wie erwartet ausgeführt werden.


Ich denke, das spricht mich gerade am meisten an. Es legt mir nahe, dass ein guter Mittelweg darin besteht, mit Mind-Mapping-Konzepten zu beginnen, um Teile zu codieren, während bestimmte Konzepte (wie DI) befolgt und verwendet werden Es oder nicht).
Dennis

1
Persönlich finde ich, dass "" moderner "" Code etwas einfacher zu profilieren ist ... Ich denke, je wartbarer ein Teil des Codes ist, desto einfacher ist es auch, ein Profil zu erstellen, aber es gibt eine Grenze, wo das Zerlegen von Dingen in kleine Teile sie macht weniger wartbar ...
AK_

25

Ohne äußerste Sorgfalt führt eine Mikrooptimierung wie diese zu nicht wartbarem Code.

Zunächst sieht es nach einer guten Idee aus, der Profiler gibt an, dass der Code schneller ist, und V & V / Test / QA sagt sogar, dass es funktioniert. Bald werden Fehler gefunden, Änderungen der Anforderungen und Verbesserungen, die nie in Betracht gezogen wurden, gefordert.

Während der Laufzeit eines Projekts verschlechtert sich der Code und wird weniger effizient. Wartungsfähiger Code wird effizienter als sein nicht wartbares Gegenstück, da er langsamer wird. Der Grund ist, dass Code Entropie aufbaut, wenn er geändert wird -

Nicht wartbarer Code hat schnell mehr toten Code, redundante Pfade und Vervielfältigung. Dies führt zu mehr Fehlern, was zu einem Zyklus der Verschlechterung des Codes führt - einschließlich seiner Leistung. In Kürze haben Entwickler ein geringes Vertrauen in die Richtigkeit der von ihnen vorgenommenen Änderungen. Dies verlangsamt sie, macht sie vorsichtig und führt im Allgemeinen zu noch mehr Entropie, da sie nur die Details behandeln, die sie sehen können

Wartungsfähiger Code mit kleinen Modulen und Komponententests ist einfacher zu ändern, nicht mehr benötigter Code ist leichter zu identifizieren und zu entfernen. Fehlerhafter Code ist auch leichter zu identifizieren, kann repariert oder zuverlässig ersetzt werden.

Am Ende kommt es also auf das Lebenszyklus-Management an und ist nicht so einfach wie "Das ist schneller, also wird es immer schneller sein."

Vor allem langsamer korrekter Code ist unendlich schneller als schneller falscher Code.


Thankx. Um dies ein Stückchen in meine Richtung zu treiben, spreche ich nicht von Mikrooptimierung, sondern von einem globalen Schritt zum Schreiben von kleinerem Code, der mehr Abhängigkeitsinjektion und damit mehr Teile der externen Funktionalität und mehr "bewegliche Teile" von Code im Allgemeinen umfasst dass alle miteinander verbunden sein müssen, damit sie funktionieren. Ich neige dazu zu denken, dass dies mehr Linkage / Connector / Variable-Passing / MOV / Push / Pop / Call / JMP-Flaum auf Hardware-Ebene erzeugt. Ich sehe auch eine Verschiebung in Richtung Codelesbarkeit, obwohl dies zu Lasten der Beeinträchtigung der Rechenzyklen auf Hardwareebene für "Fluff" geht.
Dennis

10
Das Vermeiden von Funktionsaufrufen aus Performancegründen ist absolut eine Mikrooptimierung! Ernst. Ich kann mir kein besseres Beispiel für Mikrooptimierung vorstellen. Welche Beweise haben Sie dafür, dass der Leistungsunterschied für die Art von Software, die Sie schreiben, tatsächlich von Bedeutung ist? Es hört sich so an, als hättest du keine.
RibaldEddie

17

Meines Erachtens kann es, wie Sie mit Inline betonen, bei Code-Formen auf niedrigerer Ebene wie C ++ einen Unterschied machen, aber ich sage CAN leichthin.

Die Website fasst es zusammen - es gibt keine einfache Antwort . Es hängt vom System ab, es hängt davon ab, was Ihre Anwendung tut, es hängt von der Sprache ab, es hängt vom Compiler und der Optimierung ab.

Inline kann beispielsweise die Leistung von C ++ steigern. Oft kann es nichts bewirken oder die Leistung beeinträchtigen, aber ich persönlich bin noch nie darauf gestoßen, dass ich von Geschichten gehört habe. Inline ist nichts anderes als ein Vorschlag an den Compiler zur Optimierung, der ignoriert werden kann.

Wenn Sie Programme auf höherer Ebene entwickeln, sollte der Overhead wahrscheinlich kein Problem darstellen, wenn überhaupt eines vorhanden ist. Compiler sind heutzutage extrem schlau und sollten sowieso damit umgehen. Viele Programmierer haben einen Code zum Leben: Vertrauen Sie niemals dem Compiler. Wenn dies auf Sie zutrifft, können sogar geringfügige Optimierungen vorgenommen werden, die Sie für wichtig halten. Beachten Sie jedoch, dass jede Sprache in dieser Hinsicht unterschiedlich ist. Java führt zur Laufzeit automatisch Inline-Optimierungen durch. In Javascript ist die Inline-Funktion für Ihre Webseite (im Gegensatz zu separaten Dateien) eine Steigerung, und jede Millisekunde für eine Webseite zählt möglicherweise, aber das ist eher ein E / A-Problem.

Bei Programmen auf niedrigerer Ebene, bei denen der Programmierer möglicherweise viel Maschinencode zusammen mit C ++ ausführt, kann das Abhören den Unterschied ausmachen. Spiele sind ein gutes Beispiel dafür, dass CPU-Pipelining besonders auf Konsolen von entscheidender Bedeutung ist, und so etwas wie Inline kann sich hier und da etwas summieren.

Eine gute Lektüre speziell inline: http://www.gotw.ca/gotw/033.htm


Aus der Sicht meiner Frage konzentriere ich mich nicht so sehr auf Inlining per seh, sondern auf die "codierte Verkabelung", die den Zeitprozess von CPU, Bus und E / A in Anspruch nimmt und verschiedene Codeteile miteinander verbindet. Ich frage mich, ob es irgendwann einen Punkt gibt, an dem mindestens 50% des Verkabelungscodes und 50% des tatsächlichen Codes vorhanden sind, den Sie ausführen möchten. Ich stelle mir vor, dass es selbst in dem engsten Code, den man schreiben kann, eine Menge Flusen gibt, und es scheint eine Tatsache des Lebens zu sein. Ein Großteil des eigentlichen Codes, der auf der Ebene von Bits und Bytes ausgeführt wird, ist die Logistik - Verschieben von Werten von einem Ort zu einem anderen, Springen zu einem Ort oder einem anderen und nur manchmal Hinzufügen von Werten.
Dennis

... Subtraktion oder andere geschäftliche Funktion. Genau wie das Abrollen von Schleifen einige Schleifen beschleunigen kann, weil weniger Aufwand für das Inkrementieren von Variablen anfällt, kann das Schreiben größerer Funktionen wahrscheinlich zu einer gewissen Geschwindigkeit führen, vorausgesetzt, Ihr Anwendungsfall ist dafür eingerichtet. Mein Anliegen ist es insgesamt, viele Ratschläge zum Schreiben kleinerer Codeteile zu erhalten, diese Verkabelung zu erhöhen und gleichzeitig die Lesbarkeit zu verbessern (hoffentlich) und auf Kosten des Aufblasens auf Mikroebene.
Dennis

3
@Dennis - In einer OO-Sprache besteht möglicherweise eine sehr geringe Korrelation zwischen dem, was der Programmierer schreibt (a + b) und dem generierten Code (eine einfache Addition von zwei Registern). dann Funktionsaufrufe in den Operator eines Objekts +?). So können "kleine Funktionen" auf der Ebene des Programmierers alles andere als klein sein, wenn sie einmal in Maschinencode gerendert wurden.
Michael Kohne

2
@ Tennis Ich kann sagen, dass das Schreiben von ASM-Code (direkt, nicht kompiliert) für Windows nach dem Motto "mov, mov, invoke, mov, mov, invoke" abläuft. Mit invoke als Makro, das einen durch pushes / pops umschlossenen Aufruf ausführt ... Gelegentlich führen Sie Funktionsaufrufe in Ihrem eigenen Code aus, der jedoch von allen OS-Aufrufen in den Schatten gestellt wird.
Brian Knoblauch

12

Dies ist eine Änderung im Vergleich zu den "alten" oder "schlechten" Code-Praktiken, bei denen Sie Methoden haben, die sich über 2500 Zeilen erstrecken, und große Klassen, die alles tun.

Ich glaube nicht, dass jemals jemand gedacht hat, dass dies eine gute Übung ist. Und ich bezweifle, dass die Leute, die es getan haben, es aus Performancegründen getan haben.

Ich denke, das berühmte Zitat von Donald Knuth ist hier sehr relevant:

Wir sollten kleine Wirkungsgrade vergessen, sagen wir in 97% der Fälle: Vorzeitige Optimierung ist die Wurzel allen Übels.

Verwenden Sie also in 97% Ihres Codes nur bewährte Methoden, schreiben Sie kleine Methoden (wie klein es meiner Meinung nach ist, ich denke nicht, dass alle Methoden nur wenige Zeilen umfassen sollten) usw. Für die verbleibenden 3%, in denen Leistung erzielt wird ist wichtig, messen Sie es. Und wenn Messungen zeigen, dass viele kleine Methoden Ihren Code tatsächlich erheblich verlangsamen, sollten Sie sie zu größeren Methoden kombinieren. Schreiben Sie jedoch keinen nicht wartbaren Code, nur weil er möglicherweise schneller ist.


11

Sie müssen vorsichtig sein, um erfahrenen Programmierern und dem aktuellen Denken zuzuhören. Menschen, die sich jahrelang mit massiver Software beschäftigt haben, haben etwas beizutragen.

Nach meiner Erfahrung führt Folgendes zu Verlangsamungen, und diese sind nicht gering. Sie sind Größenordnungen:

  • Die Annahme, dass jede Codezeile ungefähr so ​​lange dauert wie jede andere. Zum Beispiel cout << endlgegen a = b + c. Ersteres dauert tausende Male länger als Letzteres. Stackexchange hat viele Fragen der Form "Ich habe verschiedene Möglichkeiten zur Optimierung dieses Codes ausprobiert, aber es scheint keinen Unterschied zu machen, warum nicht?" wenn sich ein großer alter Funktionsaufruf in der Mitte befindet.

  • Die Annahme, dass ein einmal geschriebener Funktions- oder Methodenaufruf notwendig ist. Funktionen und Methoden sind einfach aufzurufen, und der Aufruf ist in der Regel sehr effizient. Das Problem ist, dass sie wie Kreditkarten sind. Sie verleiten Sie dazu, mehr auszugeben, als Sie wirklich wollen, und sie neigen dazu, zu verbergen, was Sie ausgegeben haben. Darüber hinaus verfügt große Software über mehrere Abstraktionsebenen. Selbst wenn auf jeder Ebene nur 15% Abfall anfallen, über 5 Ebenen, was zu einem Verlangsamungsfaktor von 2 führt. Die Antwort darauf besteht nicht darin, Funktionen zu entfernen oder zu schreiben Bei größeren Aufgaben ist es wichtig, sich selbst zu disziplinieren, um auf dieses Problem zu achten und es zu lösen .

  • Galoppierende Allgemeinheit. Der Wert der Abstraktion besteht darin, dass Sie mit weniger Code mehr erreichen können - das ist zumindest die Hoffnung. Diese Idee kann auf die Spitze getrieben werden. Das Problem ist zu allgemein, dass jedes Problem spezifisch ist. Wenn Sie es mit allgemeinen Abstraktionen lösen, können diese Abstraktionen nicht unbedingt die spezifischen Eigenschaften Ihres Problems ausnutzen. Ich habe zum Beispiel eine Situation erlebt, in der eine ausgefallene Prioritätswarteschlangenklasse, die bei großen Größen effizient sein kann, verwendet wurde, wenn die Länge niemals 3 überschritt!

  • Galoppierende Datenstruktur. OOP ist ein sehr nützliches Paradigma, aber es regt nicht dazu an, die Datenstruktur zu minimieren, sondern es regt dazu an, zu versuchen, deren Komplexität zu verbergen. Zum Beispiel gibt es das Konzept der "Benachrichtigung", bei der A, wenn Datum A auf irgendeine Weise geändert wird, ein Benachrichtigungsereignis ausgibt, so dass B und C sich auch selbst ändern können, um das gesamte Ensemble konsistent zu halten. Dies kann sich über viele Schichten ausbreiten und die Kosten der Modifikation enorm erhöhen. Dann ist es durchaus möglich, dass der Wechsel zu A bald wieder rückgängig gemacht wirdoder zu einer anderen Änderung geändert, was bedeutet, dass der Aufwand für die Konsistenz des Ensembles erneut zu leisten ist. Dies ist die Wahrscheinlichkeit von Fehlern in all diesen Benachrichtigungs-Handlern und der Zirkularität usw. Es ist weitaus besser, die Datenstruktur normal zu halten, damit Änderungen nur an einer Stelle vorgenommen werden müssen. Wenn nicht normalisierte Daten nicht vermieden werden können, ist es besser, regelmäßige Durchläufe durchzuführen, um die Inkonsistenz zu reparieren, als vorzugeben, dass sie mit einer kurzen Leine konsistent gehalten werden können.

... wenn ich an mehr denke, werde ich es hinzufügen.


8

Die kurze Antwort lautet "Ja". Im Allgemeinen ist der Code etwas langsamer.

Manchmal werden bei einer ordnungsgemäßen OO-artigen Umgestaltung Optimierungen sichtbar, die den Code schneller machen. Ich habe an einem Projekt gearbeitet, in dem wir einen komplexen Java-Algorithmus entwickelt haben, der viel besser aussieht, als verschachtelte Anordnungen von Objekten. Indem wir jedoch den Zugriff auf die Datenstrukturen besser isolieren und einschränken, konnten wir von riesigen Doppel-Arrays (mit Nullen für leere Ergebnisse) zu besser organisierten Doppel-Arrays mit NaNs für leere Ergebnisse wechseln. Dies ergab einen 10-fachen Geschwindigkeitsgewinn.

Nachtrag: Im Allgemeinen sollte kleinerer, besser strukturierter Code für Multithreading besser geeignet sein. Dies ist der beste Weg, um größere Geschwindigkeitssteigerungen zu erzielen.


1
Ich verstehe nicht, warum ein Wechsel von Doubles zu doubles besser strukturierten Code erfordern würde .
Svick

Wow, eine Gegenstimme? Der ursprüngliche Client-Code hat sich nicht mit Double.NaNs befasst, sondern nach Nullen gesucht, um leere Werte darzustellen. Nach der Umstrukturierung konnten wir dies (über die Kapselung) mit den Gettern der verschiedenen Algorithmusergebnisse handhaben. Sicher, wir hätten den Client-Code umschreiben können, aber das war einfacher.
user949300

Ich war nicht derjenige, der diese Antwort abgelehnt hat.
Svick

7

Bei der Programmierung geht es unter anderem um Kompromisse . Aufgrund dieser Tatsache neige ich dazu, mit Ja zu antworten könnte langsamer sein. Aber denken Sie darüber nach, was Sie als Gegenleistung erhalten. Das Erhalten von lesbarem, wiederverwendbarem und leicht modifizierbarem Code überwindet leicht mögliche Nachteile.

Wie @ user949300 erwähnt , ist es einfacher, Bereiche zu erkennen, die mit einem solchen Ansatz algorithmisch oder architektonisch verbessert werden können. Diese zu verbessern ist in der Regel weitaus vorteilhafter und effektiver, als keinen möglichen OO- oder Funktionsaufruf-Overhead zu haben (was, wie ich wette, bereits ein Rauschen ist).


Ich stelle mir auch vor, dass gute Optimierungen an ASM vorgenommen werden können, bevor es tatsächlich auf der Hardware ausgeführt wird.

Wenn mir so etwas in den Sinn kommt, erinnere ich mich, dass die jahrzehntelangen Arbeiten der klügsten Leute an Compilern wahrscheinlich Tools wie GCC entwickeln, die weitaus besser als ich sind, um Maschinencode zu generieren. Ich schlage vor, dass Sie sich darüber keine Sorgen machen, es sei denn, Sie arbeiten an irgendwelchen Sachen, die mit Mikrocontrollern zu tun haben.

Ich gehe davon aus, dass das Hinzufügen von immer mehr Funktionen und immer mehr Objekten und Klassen in einem Code dazu führt, dass immer mehr Parameter zwischen kleineren Codeteilen übertragen werden.

Wenn Sie davon ausgehen, dass bei der Optimierung Zeitverschwendung entsteht, benötigen Sie Fakten zur Code-Leistung. Finden Sie heraus, wo Ihr Programm die meiste Zeit mit speziellen Tools verbracht hat, optimieren Sie diese und wiederholen Sie sie.


Alles zusammenzufassen; Überlassen Sie es dem Compiler, sich auf wichtige Dinge wie die Verbesserung Ihrer Algorithmen und Datenstrukturen zu konzentrieren. Alle Muster, die Sie in Ihrer Frage erwähnt haben, sind vorhanden, um Ihnen dabei zu helfen. Verwenden Sie sie.

PS: Diese zwei Gespräche von Crockford gingen mir durch den Kopf und ich denke, sie sind irgendwie verwandt. Erstens ist es eine sehr kurze CS-Geschichte (was bei jeder exakten Wissenschaft immer gut zu wissen ist); und zweitens geht es darum, warum wir gute Dinge ablehnen.


Diese Antwort gefällt mir am besten. Menschen sind schrecklich, wenn es darum geht, den Compiler zu erraten und zu untersuchen, wo die Engpässe liegen. Natürlich sollten Sie sich der Komplexität der Big-O-Zeit bewusst sein, aber die große 100-Zeilen-Spaghetti-Methode im Vergleich zu einem Factory-Methodenaufruf und dem Versenden einiger virtueller Methoden sollte niemals eine Diskussion sein. Leistung ist in diesem Fall überhaupt nicht interessant. Außerdem ist es ein großes Plus, festzustellen, dass das "Optimieren" ohne harte Fakten und Messungen Zeitverschwendung ist.
Sara

4

Ich glaube, die Trends, die Sie erkennen, sprechen für die Wahrheit in Bezug auf die Entwicklung von Software - Programmiererzeit ist teurer als CPU-Zeit. Bisher sind Computer nur schneller und billiger geworden, aber ein Wirrwarr von Anwendungen kann Hunderte, wenn nicht Tausende von Arbeitsstunden in Anspruch nehmen, um sie zu ändern. Angesichts der Kosten für Gehälter, Sozialleistungen, Büroflächen usw. ist es kostengünstiger, Code zu haben, der zwar etwas langsamer ausgeführt wird, aber schneller und sicherer zu ändern ist.


1
Ich stimme zu, aber mobile Geräte werden so populär, dass ich denke, dass sie eine große Ausnahme darstellen. Obwohl die Verarbeitungsleistung zunimmt, können Sie keine iPhone-App erstellen und erwarten, dass Sie Speicher wie auf einem Webserver hinzufügen können.
JeffO

3
@JeffO: Ich bin anderer Meinung. Mobile Geräte mit Quad-Core-Prozessoren sind jetzt normal. Die Leistung (insbesondere, da sie die Akkulaufzeit beeinträchtigt) ist zwar ein Problem, aber weniger wichtig als die Stabilität. Ein langsames Handy oder Tablet erhält schlechte Bewertungen, ein instabiles wird geschlachtet. Mobile Apps sind sehr dynamisch und ändern sich fast täglich - die Quelle muss mithalten.
Mattnz

1
@JeffO: Für das, was es wert ist, ist die auf Android verwendete virtuelle Maschine ziemlich schlecht. Gemäß Benchmarks könnte es eine Größenordnung langsamer sein als nativer Code (während die Besten der Rasse normalerweise nur ein bisschen langsamer sind). Rate was? Niemanden interessiert's. Das Schreiben der Anwendung ist schnell und die CPU sitzt da, dreht die Daumen und wartet sowieso 90% der Zeit auf Benutzereingaben.
Jan Hudec

Leistung ist mehr als reine CPU-Benchmarks. Mein Android-Handy funktioniert einwandfrei, es sei denn, der AV scannt gerade ein Update und scheint dann länger zu hängen, als ich es mag. Es ist ein Quad-Core-2-GB-RAM-Modell! Heute ist die Bandbreite (egal ob Netzwerk oder Speicher) wahrscheinlich der Hauptengpass. Wahrscheinlich dreht Ihre superschnelle CPU 99% der Zeit mit den Daumen und die allgemeine Erfahrung ist immer noch schlecht.
gbjbaanb

4

Nun, vor mehr als 20 Jahren, was ich vermute, dass Sie nicht neu und nicht "alt oder schlecht" nennen, bestand die Regel darin, Funktionen so klein zu halten, dass sie auf eine gedruckte Seite passen. Wir hatten damals Punktmatrixdrucker, so dass die Zeilenzahl im Allgemeinen nur ein oder zwei Möglichkeiten für die Zeilenzahl pro Seite festlegte ... definitiv weniger als 2500 Zeilen.

Sie fragen viele Seiten des Problems: Wartbarkeit, Leistung, Testbarkeit und Lesbarkeit. Je mehr Sie sich an der Leistung orientieren, desto weniger verwaltbar und lesbar wird der Code. Sie müssen also Ihr Komfortniveau finden, das für jeden einzelnen Programmierer unterschiedlich sein kann und sein wird.

Was den vom Compiler erzeugten Code anbelangt (Maschinencode, wenn Sie so wollen), so ist die Möglichkeit größer, dass Zwischenwerte in Registern in den Stapel geschrieben werden müssen, je größer die Funktion ist. Wenn Stapelrahmen verwendet werden, liegt der Stapelverbrauch in größeren Stücken. Je kleiner die Funktionen sind, desto größer ist die Chance, dass die Daten in den Registern gespeichert bleiben und desto weniger Abhängigkeit vom Stapel. Kleinere Stapelbrocken werden natürlich pro Funktion benötigt. Stapelrahmen haben Vor- und Nachteile für die Leistung. Je kleiner die Funktionen, desto mehr Funktionen werden eingerichtet und bereinigt. Natürlich hängt es auch davon ab, wie Sie kompilieren, welche Möglichkeiten Sie dem Compiler geben. Sie können 250 10-Zeilen-Funktionen anstelle einer 2500-Zeilen-Funktion haben, die eine 2500-Zeilen-Funktion, die der Compiler erhalten wird, wenn er die gesamte Sache optimieren kann. Wenn Sie diese 250 Funktionen mit 10 Zeilen verwenden und sie auf 2, 3, 4, 250 separate Dateien verteilen, kompilieren Sie jede Datei separat, dann kann der Compiler fast so viel toten Code nicht optimieren, wie er haben könnte. Unter dem Strich gibt es für beide Vor- und Nachteile, und es ist nicht möglich, eine allgemeine Regel für diesen oder jenen Weg aufzustellen.

Funktionen in angemessener Größe, die eine Person auf einem Bildschirm oder einer Seite sehen kann (in einer angemessenen Schriftart), können besser verwendet und verstanden werden als Code, der ziemlich groß ist. Wenn es sich jedoch nur um eine kleine Funktion mit Aufrufen von vielen anderen kleinen Funktionen handelt, die viele andere kleine Funktionen aufrufen, benötigen Sie mehrere Fenster oder Browser, um diesen Code zu verstehen, sodass Sie auf der Seite der Lesbarkeit nichts gekauft haben.

Der Unix-Weg ist, mit meinem Begriff schön polierte Legoblöcke herzustellen. Warum sollten Sie so viele Jahre nach dem Verzicht auf Bänder eine Bandfunktion verwenden? Da der Blob seine Aufgabe sehr gut erledigt hat und auf der Rückseite können wir die Bandschnittstelle durch eine Dateischnittstelle ersetzen und das Fleisch des Programms ausnutzen. Warum sollte man eine CD-ROM-Brennsoftware neu schreiben, nur weil SCSI als dominante Schnittstelle durch IDE ersetzt wurde? Profitieren Sie erneut von den polierten Unterblöcken und ersetzen Sie ein Ende durch einen neuen Schnittstellenblock. (Verstehen Sie auch, dass die Hardware-Designer einfach einen Schnittstellenblock auf die Hardware-Designs geklebt haben, um in einigen Fällen ein SCSI-Laufwerk mit einer IDE-zu-SCSI-Schnittstelle zu versehen.) Um dies zu verkürzen Bauen Sie vernünftig große polierte Legoblöcke mit jeweils einem genau definierten Zweck und genau definierten Ein- und Ausgängen. Sie können Tests um diese Legoblöcke wickeln und dann denselben Block und dieselbe Benutzeroberfläche und dieselben Betriebssystemschnittstellen um denselben Block und denselben Block wickeln. Theoretisch ist dies gut getestet und muss nicht getestet werden, sondern es werden lediglich die zusätzlichen neuen Blöcke hinzugefügt an jedem Ende. Solange alle Schnittstellen Ihrer Blöcke gut gestaltet sind und die Funktionalität gut verstanden wird, können Sie eine Vielzahl von Dingen mit einem Minimum an Klebstoff erstellen. genau wie mit blauen und roten sowie schwarzen und gelben legoblöcken bekannter größen und formen kann man sehr viele dinge herstellen. Solange alle Schnittstellen Ihrer Blöcke gut gestaltet sind und die Funktionalität gut verstanden wird, können Sie eine Vielzahl von Dingen mit einem Minimum an Klebstoff erstellen. genau wie mit blauen und roten sowie schwarzen und gelben legoblöcken bekannter größen und formen kann man sehr viele dinge herstellen. Solange alle Schnittstellen Ihrer Blöcke gut gestaltet sind und die Funktionalität gut verstanden wird, können Sie eine Vielzahl von Dingen mit einem Minimum an Klebstoff erstellen. genau wie mit blauen und roten sowie schwarzen und gelben legoblöcken bekannter größen und formen kann man sehr viele dinge herstellen.

Jede Person ist anders, ihre Definition von poliert und gut definiert und getestet und lesbar variieren. Zum Beispiel ist es für einen Professor nicht unvernünftig, Programmierregeln zu diktieren, nicht weil sie für Sie als Fachmann schlecht oder schlecht sein können, sondern in einigen Fällen, um den Professoren oder studentischen Hilfskräften das Lesen und Bewerten Ihres Codes zu erleichtern ... Sie stellen beruflich mit gleicher Wahrscheinlichkeit fest, dass für jeden Job aus verschiedenen Gründen unterschiedliche Regeln gelten. In der Regel haben einer oder mehrere Machthaber eine Meinung zu etwas Richtigem oder Falschem, und mit dieser Macht können sie diktieren, was Sie tun es dort weg (oder aufhören, gefeuert werden oder irgendwie an die Macht kommen). Diese Regeln basieren ebenso oft auf Meinungen wie auf Fakten über Lesbarkeit, Leistung, Testbarkeit und Portabilität.


"Sie müssen also Ihr Komfortniveau finden, das für jeden einzelnen Programmierer unterschiedlich sein kann und sein wird." Ich denke, das Optimierungsniveau sollte von den Leistungsanforderungen jedes einzelnen Codeteils bestimmt werden, nicht von dem, was jedem Programmierer gefällt.
Svick

Der Compiler ist sicher, vorausgesetzt, der Programmierer hat dem Compiler befohlen, die Compiler zu optimieren. Im Allgemeinen wird standardmäßig nicht optimiert (in der Befehlszeile hat die IDE möglicherweise einen anderen Standard, wenn sie verwendet wird). Aber der Programmierer weiß möglicherweise nicht genug, um die Größe der Funktion zu optimieren, und bei so vielen Zielen, wo wird sie zwecklos? Die Hand-Tuning-Leistung wirkt sich tendenziell negativ auf die Lesbarkeit aus. Insbesondere die Wartbarkeit und die Testbarkeit können in beide Richtungen gehen.
old_timer

2

Kommt drauf an, wie schlau dein Compiler ist. Im Allgemeinen ist der Versuch, den Optimierer zu überlisten, eine schlechte Idee und kann dem Compiler tatsächlich Optimierungsmöglichkeiten nehmen. Für den Anfang haben Sie wahrscheinlich keine Ahnung, was es tun kann, und das Meiste, was Sie tun, beeinflusst tatsächlich, wie gut es das auch tut.

Vorzeitige Optimierung ist die Vorstellung von Programmierern, die dies versuchen und am Ende schwer zu verwaltenden Code haben, der sich nicht auf dem kritischen Pfad befand, auf dem sie sich befanden. Der Versuch, so viel CPU wie möglich herauszuholen, wenn Ihre App die meiste Zeit tatsächlich blockiert ist und auf E / A-Ereignisse wartet, ist etwas, was ich zum Beispiel oft sehe.

Am besten ist es, auf Korrektheit zu codieren und einen Profiler zu verwenden, um tatsächliche Leistungsengpässe zu finden und diese zu beheben, indem analysiert wird, was sich auf dem kritischen Pfad befindet und ob es verbessert werden kann. Oft reichen ein paar einfache Korrekturen aus.


0

[Computer-Zeit-Element]

Beeinflusst das Umgestalten in Richtung einer lockeren Kopplung und kleinerer Funktionen die Geschwindigkeit des Codes?

Ja tut es. Es liegt jedoch an Interpreten, Compilern und JIT-Compilern, diesen "Naht- / Verdrahtungscode" zu reduzieren, und einige tun dies besser als andere, andere jedoch nicht.

Das Problem mit mehreren Dateien erhöht den E / A-Aufwand, sodass sich dies auch auf die Geschwindigkeit auswirkt (in Computerzeit).

[Mensch-Geschwindigkeit-Element]

(Und sollte es mich interessieren?)

Nein, das sollte dich nicht interessieren. Computer und Schaltkreise sind heutzutage sehr schnell und andere Faktoren wie Netzwerklatenz, Datenbank-E / A und Caching spielen eine wichtige Rolle.

Wenn sich die Ausführung von nativem Code also 2x - 4x verlangsamt, wird dies häufig durch diese anderen Faktoren überdeckt.

Für das Laden mehrerer Dateien sorgen häufig verschiedene Caching-Lösungen. Das Laden und Zusammenführen von Dingen kann länger dauern, aber bei statischen Dateien funktioniert das Zwischenspeichern beim nächsten Mal so, als würde eine einzelne Datei geladen. Caching ist eine Lösung für das Laden mehrerer Dateien.


0

DIE ANTWORT (falls du es verpasst hast)

Ja, Sie sollten sich darum kümmern, aber darum, wie Sie den Code schreiben, und nicht um die Leistung.

Zusamenfassend

Kümmere dich nicht um Leistung

Im Rahmen der Frage kümmern sich bereits klügere Compiler und Dolmetscher darum

Achten Sie darauf, dass Sie wartbaren Code schreiben

Code, bei dem die Wartungskosten auf dem Niveau eines angemessenen menschlichen Verständnisses liegen. Schreiben Sie also nicht 1000 kleinere Funktionen, die den Code unverständlich machen, selbst wenn Sie jede verstehen, und schreiben Sie nicht eine Gott-Objektfunktion, die zu groß ist, um sie zu verstehen, sondern schreiben Sie 10 ausgereifte Funktionen, die für einen Menschen sinnvoll sind und sind leicht zu pflegen.

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.