Einstiegsfrage des Ingenieurs zur Speicherverwaltung

9

Es ist einige Monate her, seit ich meine Position als Softwareentwickler für Einsteiger begonnen habe. Nachdem ich einige Lernkurven hinter mir habe (z. B. Sprache, Jargon, Syntax von VB und C #), konzentriere ich mich auf esoterischere Themen, um bessere Software zu schreiben.

Eine einfache Frage, die ich einem Kollegen stellte, wurde mit "Ich konzentriere mich auf die falschen Dinge" beantwortet. Obwohl ich diesen Kollegen respektiere, bin ich nicht der Meinung, dass dies eine "falsche Sache" ist, auf die man sich konzentrieren sollte.

Hier war der Code (in VB) und gefolgt von der Frage.

Hinweis: Die Funktion GenerateAlert () gibt eine Ganzzahl zurück.

Dim alertID as Integer = GenerateAlert()
_errorDictionary.Add(argErrorID, NewErrorInfo(Now(), alertID))    

vs ...

 _errorDictionary.Add(argErrorID, New ErrorInfo(Now(), GenerateAlert()))

Ich habe das letztere ursprünglich geschrieben und es mit der "Dim alertID" umgeschrieben, damit es jemand anderem leichter fällt, es zu lesen. Aber hier war meine Sorge und Frage:

Sollte man dies mit der Dim AlertID schreiben, würde es tatsächlich mehr Speicherplatz beanspruchen; endlich, aber mehr, und sollte diese Methode oft aufgerufen werden, könnte sie zu einem Problem führen? Wie wird .NET mit diesem Objekt umgehen? AlertID. Außerhalb von .NET sollte man das Objekt nach Gebrauch manuell entsorgen (gegen Ende des Sub).

Ich möchte sicherstellen, dass ich ein sachkundiger Programmierer werde, der sich nicht nur auf die Speicherbereinigung verlässt. Bin ich über das nachgedacht? Konzentriere ich mich auf die falschen Dinge?

Sean Hobbs
quelle
1
Ich könnte leicht sagen, dass er 100% ist, da die erste Version besser lesbar ist. Ich würde wetten, dass der Compiler sich sogar um das kümmert, worum es Ihnen geht. Auch wenn dies nicht der Fall ist, optimieren Sie vorzeitig.
Rig
6
Ich bin mir überhaupt nicht sicher, ob es wirklich mehr Speicher mit einer anonymen Ganzzahl im Vergleich zu einer benannten Ganzzahl verbrauchen würde. In jedem Fall ist dies wirklich eine vorzeitige Optimierung. Wenn Sie sich um die Effizienz auf dieser Ebene sorgen müssen (ich bin mir fast sicher, dass Sie dies nicht tun), benötigen Sie möglicherweise C ++ anstelle von C #. Es ist gut, Leistungsprobleme und das, was unter der Haube passiert, zu verstehen, aber dies ist ein kleiner Baum in einem großen Wald.
Psr
5
Die benannte oder anonyme Ganzzahl würde nicht mehr Speicher belegen, zumal eine anonyme Ganzzahl nur eine benannte Ganzzahl ist, die SIE nicht benannt haben (der Compiler muss sie noch benennen). Die benannte Ganzzahl hätte höchstens einen anderen Gültigkeitsbereich, sodass sie möglicherweise länger lebt. Die anonyme Ganzzahl würde nur so lange leben, wie die Methode sie benötigt, die genannte würde so lange leben, wie die übergeordnete Methode sie benötigt.
Joel Etherton
Mal sehen ... Wenn Integer eine Klasse ist, wird sie auf dem Heap zugewiesen. Die lokale Variable (höchstwahrscheinlich auf dem Stapel) enthält einen Verweis darauf. Die Referenz wird an das Objekt errorDictionary übergeben. Wenn die Laufzeit eine Referenzzählung durchführt (oder eine solche), wird sie (das Objekt) vom Heap freigegeben, wenn keine Referenzen mehr vorhanden sind. Alles auf dem Stapel wird automatisch "freigegeben", sobald die Methode beendet wird. Wenn es sich um ein Grundelement handelt, landet es (höchstwahrscheinlich) auf dem Stapel.
Paul
Ihr Kollege hatte Recht: Das von Ihrer Frage aufgeworfene Problem sollte nicht die Optimierung sein, sondern die Lesbarkeit .
Haylem

Antworten:

25

"Vorzeitige Optimierung ist die Wurzel allen Übels (oder zumindest des größten Teils davon) in der Programmierung." - Donald Knuth

Wenn es um Ihren ersten Durchgang geht, schreiben Sie einfach Ihren Code, damit er korrekt und sauber ist. Wenn später festgestellt wird, dass Ihr Code leistungskritisch ist (es gibt Tools, mit denen dies als Profiler bezeichnet werden kann), kann er neu geschrieben werden. Wenn Ihr Code nicht als leistungskritisch eingestuft wird, ist die Lesbarkeit weitaus wichtiger.

Lohnt es sich, sich mit diesen Themen Leistung und Optimierung zu befassen? Absolut, aber nicht auf den Dollar Ihres Unternehmens, wenn es unnötig ist.

Christopher Berman
quelle
1
Auf wem sollte der Dollar sonst sein? Ihr Arbeitgeber profitiert eher von der Verbesserung Ihrer Fähigkeiten als Sie.
Marcin
Themen, die nicht direkt zu Ihrer aktuellen Aufgabe beitragen? Sie sollten diese Dinge in Ihrer Freizeit verfolgen. Wenn ich mich hinsetzte und jeden CompSci-Artikel recherchierte, der meine Neugier im Laufe des Tages weckte, würde ich nichts tun. Dafür sind meine Abende da.
Christopher Berman
2
Seltsam. Einige von uns haben ein persönliches Leben, und wie ich bereits sagte, profitiert der Arbeitgeber in erster Linie von der Forschung. Der Schlüssel ist, nicht den ganzen Tag damit zu verbringen.
Marcin
6
Schön für dich. Es macht es jedoch nicht wirklich zu einer universellen Regel. Wenn Sie Ihre Mitarbeiter vom Lernen bei der Arbeit abhalten, haben Sie sie lediglich vom Lernen abgehalten und sie ermutigt, einen anderen Arbeitgeber zu finden, der tatsächlich für die Personalentwicklung bezahlt.
Marcin
2
Ich verstehe die in den obigen Kommentaren angegebenen Meinungen. Ich möchte darauf hinweisen, dass ich während meiner Mittagspause gefragt habe. :). Nochmals vielen Dank für Ihre Beiträge hier und auf der gesamten Stack Exchange-Website. es ist von unschätzbarem Wert!
Sean Hobbs
5

Ja, für ein durchschnittliches .NET-Programm ist es ein Überdenken. Es kann vorkommen, dass Sie genau wissen möchten, was in .NET vor sich geht, aber dies ist relativ selten.

Einer der schwierigen Übergänge, die ich hatte, war in den 90er Jahren der Wechsel von C und MASM zur Programmierung in klassischem VB. Ich war es gewohnt, alles auf Größe und Geschwindigkeit zu optimieren. Ich musste dieses Denken größtenteils loslassen und VB es tun lassen, um effektiv zu sein.

jfrankcarr
quelle
5

Wie mein Kollege immer sagte:

  1. Bring es zum Laufen
  2. Beheben Sie alle Fehler, damit es einwandfrei funktioniert
  3. Machen Sie es fest
  4. Wenden Sie die Optimierung an, wenn die Leistung langsam ist

Mit anderen Worten, denken Sie immer an KISS (halten Sie es einfach dumm). Aufgrund von Überentwicklung kann das Überdenken einer Codelogik ein Problem sein, um die Logik beim nächsten Mal zu ändern. Es ist jedoch immer empfehlenswert, den Code sauber und einfach zu halten .

Mit der Zeit und Erfahrung würden Sie jedoch besser wissen, welcher Code riecht und bald optimiert werden müsste.

Yusubov
quelle
3

Sollte man dies mit der Dim AlertID schreiben

Lesbarkeit ist wichtig. Obwohl in Ihrem Beispiel, ich bin nicht sicher , dass Sie wirklich die Dinge zu machen , Das besser lesbar. GenerateAlert () hat einen guten Namen und fügt nicht viel Rauschen hinzu. Es gibt wahrscheinlich bessere Verwendungsmöglichkeiten für Ihre Zeit.

es würde tatsächlich mehr Speicherplatz beanspruchen;

Ich vermute es nicht. Dies ist eine relativ einfache Optimierung für den Compiler.

Sollte diese Methode mehrmals aufgerufen werden, könnte dies zu einem Problem führen?

Die Verwendung einer lokalen Variablen als Vermittler hat keine Auswirkungen auf den Garbage Collector. Wenn der Speicher von GenerateAlert () new aktiv ist, ist dies von Bedeutung. Dies ist jedoch unabhängig von der lokalen Variablen von Bedeutung oder nicht.

Wie wird .NET mit diesem Objekt umgehen? AlertID.

AlertID ist kein Objekt. Das Ergebnis von GenerateAlert () ist das Objekt. AlertID ist die Variable. Wenn es sich um eine lokale Variable handelt, handelt es sich lediglich um einen mit der Methode verknüpften Speicherplatz, um den Überblick zu behalten.

Außerhalb von .NET sollte das Objekt nach Gebrauch manuell entsorgt werden

Dies ist eine schwierigere Frage, die vom jeweiligen Kontext und der Besitzersemantik der von GenerateAlert () bereitgestellten Instanz abhängt. Was auch immer die Instanz erstellt hat, sollte sie im Allgemeinen löschen. Ihr Programm würde wahrscheinlich erheblich anders aussehen, wenn es unter Berücksichtigung der manuellen Speicherverwaltung entwickelt würde.

Ich möchte sicherstellen, dass ich ein sachkundiger Programmierer werde, der nicht nur die Speicherbereinigung weiterleitet. Bin ich über das nachgedacht? Konzentriere ich mich auf die falschen Dinge?

Ein guter Programmierer verwendet die ihm zur Verfügung stehenden Werkzeuge, einschließlich des Garbage Collectors. Es ist besser, Dinge zu überdenken, als ahnungslos zu leben. Sie konzentrieren sich vielleicht auf die falschen Dinge, aber da wir hier sind, können Sie es genauso gut lernen.

Telastyn
quelle
2

Lass es funktionieren, mach es sauber, mach es FEST, dann mach es so schnell, wie es funktionieren muss .

Das sollte die normale Reihenfolge sein. Ihre allererste Priorität ist es, etwas zu schaffen, das die Abnahmetests besteht, die die Anforderungen nicht erfüllen. Dies ist Ihre erste Priorität, da dies die erste Priorität Ihres Kunden ist. Erfüllung der funktionalen Anforderungen innerhalb der Entwicklungsfristen. Die nächste Priorität besteht darin, sauberen, lesbaren Code zu schreiben, der leicht zu verstehen ist und daher von Ihrer Nachwelt ohne WTFs gepflegt werden kann, wenn dies erforderlich wird (es ist fast nie eine Frage des "Wenn"; Sie oder jemand, nachdem Sie gehen müssen) wieder rein und etwas ändern / reparieren). Die dritte Priorität besteht darin, den Code an die SOLID-Methode anzupassen (oder GRASP, wenn Sie dies bevorzugen), die Code in modulare, wiederverwendbare, austauschbare Blöcke unterteilt, die wiederum die Wartung unterstützen (sie können nicht nur verstehen, was Sie getan haben und warum). Es gibt jedoch klare Linien, entlang derer ich Codeteile chirurgisch entfernen und ersetzen kann. Die letzte Priorität ist die Leistung; Wenn Code wichtig genug ist, um den Leistungsspezifikationen zu entsprechen, ist er mit ziemlicher Sicherheit wichtig genug, um zuerst korrekt, sauber und FEST gemacht zu werden.

In Anlehnung an Christopher (und Donald Knuth) ist "vorzeitige Optimierung die Wurzel allen Übels". Darüber hinaus sind die von Ihnen in Betracht gezogenen Optimierungen sowohl geringfügig (ein Verweis auf Ihr neues Objekt wird auf dem Stapel erstellt, unabhängig davon, ob Sie ihm im Quellcode einen Namen geben oder nicht) als auch von einem Typ, der möglicherweise keinen Unterschied bei der Kompilierung verursacht IL. Variablennamen werden nicht in die IL übertragen. Da Sie die Variable also unmittelbar vor ihrer ersten (und wahrscheinlich einzigen) Verwendung deklarieren, würde ich etwas Biergeld darauf wetten, dass die IL zwischen Ihren beiden Beispielen identisch ist. Ihr Mitarbeiter hat also 100% Recht. Sie suchen an der falschen Stelle, wenn Sie die benannte Variable im Vergleich zur Inline-Instanziierung suchen, um etwas zu optimieren.

Mikrooptimierungen in .NET sind es fast nie wert (ich spreche von 99,99% der Fälle). In C / C ++ vielleicht, WENN Sie wissen, was Sie tun. Wenn Sie in einer .NET-Umgebung arbeiten, sind Sie bereits weit genug vom Metall der Hardware entfernt, sodass die Codeausführung einen erheblichen Aufwand verursacht. Angesichts der Tatsache, dass Sie sich bereits in einer Umgebung befinden, die angibt, dass Sie die Geschwindigkeit der Blasenbildung aufgegeben haben und stattdessen "richtigen" Code schreiben möchten, ist die Komplexität einer .NET-Umgebung nicht schnell genug zu hoch, oder Sie sollten überlegen, es zu parallelisieren. Hier sind einige grundlegende Hinweise zur Optimierung. Ich garantiere Ihnen, dass Ihre Produktivität bei der Optimierung (Geschwindigkeit, die für die aufgewendete Zeit gewonnen wird) in die Höhe schnellen wird:

  • Das Ändern der Funktionsform ist wichtiger als das Ändern der Koeffizienten - WRT Big-Oh-Komplexität. Sie können die Anzahl der Schritte, die in einem N 2 -Algorithmus ausgeführt werden müssen, um die Hälfte reduzieren , und Sie haben immer noch einen Algorithmus mit quadratischer Komplexität, obwohl er ausgeführt wird die halbe Zeit früher. Wenn dies die untere Grenze der Komplexität für diese Art von Problem ist, sei es auch so, aber wenn es eine NlogN-, lineare oder logarithmische Lösung für dasselbe Problem gibt, werden Sie mehr gewinnen, wenn Sie Algorithmen wechseln, um die Komplexität zu reduzieren, als wenn Sie diejenige optimieren, die Sie haben.
  • Nur weil man nicht die Komplexität sehen kann, bedeutet nicht , dass es nicht Sie kostet - Viele des eleganteste Einzeiler im Wort führen schrecklich (zum Beispiel der Regex prime - Checker ist eine exponentielle Komplexität Funktion, während eines effizienten Die Primbewertung, bei der die Zahl durch alle Primzahlen geteilt wird, die kleiner als ihre Quadratwurzel sind, liegt in der Größenordnung von O (Nlog (sqrt (N))). Linq ist eine großartige Bibliothek, da sie den Code vereinfacht, im Gegensatz zu einer SQL-Engine jedoch das .Net Der Compiler wird nicht versuchen, den effizientesten Weg zur Ausführung Ihrer Abfrage zu finden. Sie müssen wissen, was passiert, wenn Sie eine Methode verwenden, und warum eine Methode möglicherweise schneller ist, wenn sie früher (oder später) in der Kette platziert wird, während sie produziert die gleichen Ergebnisse.
  • OTOH, es gibt fast immer einen Kompromiss zwischen Quellkomplexität und Laufzeitkomplexität - SelectionSort ist sehr einfach zu implementieren; Sie könnten es wahrscheinlich in 10LOC oder weniger tun. MergeSort ist etwas komplexer, Quicksort noch komplexer und RadixSort noch komplexer. Wenn jedoch die Codierungskomplexität der Algorithmen (und damit die Entwicklungszeit im Voraus) zunimmt, nimmt die Laufzeitkomplexität ab. MergeSort und QuickSort sind NlogN, und RadixSort wird im Allgemeinen als linear betrachtet (technisch gesehen ist es NlogM, wobei M die größte Zahl in N ist).
  • Schnell brechen - Wenn es eine Prüfung gibt, die kostengünstig durchgeführt werden kann, die mit großer Wahrscheinlichkeit wahr ist und bedeutet, dass Sie weitermachen können, führen Sie diese Prüfung zuerst durch. Wenn sich Ihr Algorithmus beispielsweise nur um Zahlen kümmert, die mit 1, 2 oder 3 enden, ist der wahrscheinlichste Fall (bei vollständig zufälligen Daten) eine Zahl, die mit einer anderen Ziffer endet. Testen Sie also, dass die Zahl NICHT mit endet 1, 2 oder 3, bevor überprüft wird, ob die Zahl mit 1, 2 oder 3 endet. Wenn für eine Logik A & B erforderlich ist und P (A) = 0,9, während P (B) = 0,1 ist, überprüfen Sie B zuerst, es sei denn, wenn! A dann! B (wie if(myObject != null && myObject.someProperty == 1)) oder B mehr als 9-mal länger als A benötigt, um ( if(myObject != null && some10SecondMethodReturningBool())) zu bewerten .
  • Stellen Sie keine Frage, auf die Sie bereits die Antwort kennen. Wenn Sie eine Reihe von "Durchfall" -Bedingungen haben und eine oder mehrere dieser Bedingungen von einer einfacheren Bedingung abhängen, die ebenfalls überprüft werden muss, überprüfen Sie niemals beide diese unabhängig voneinander. Wenn Sie beispielsweise einen Scheck haben, für den A erforderlich ist, und einen Scheck, für den A & B erforderlich ist, sollten Sie A prüfen, und wenn dies zutrifft, sollten Sie B prüfen. Wenn! A, dann! A && B, also kümmern Sie sich nicht einmal darum.
  • Je öfter Sie etwas tun, desto mehr sollten Sie darauf achten, wie es gemacht wird. Dies ist auf vielen Ebenen ein allgemeines Thema in der Entwicklung. Im allgemeinen Entwicklungssinn: "Wenn eine gemeinsame Aufgabe zeitaufwändig oder umständlich ist, machen Sie sie so lange, bis Sie sowohl frustriert als auch kompetent genug sind, um einen besseren Weg zu finden." Je öfter ein ineffizienter Algorithmus ausgeführt wird, desto mehr an Code gewinnen Sie durch Optimierung. Es gibt Profiling-Tools, die eine binäre Assembly und ihre Debug-Symbole verwenden und Ihnen nach Durchlaufen einiger Anwendungsfälle zeigen können, welche Codezeilen am häufigsten ausgeführt wurden. Diese Linien und die Linien, die diese Linien führen, sollten Sie am meisten beachten, da jede Effizienzsteigerung, die Sie dort erzielen, vervielfacht wird.
  • Ein komplexerer Algorithmus sieht aus wie ein weniger komplexer Algorithmus, wenn Sie genügend Hardware darauf werfen . Manchmal müssen Sie nur feststellen, dass sich Ihr Algorithmus den technischen Grenzen des Systems (oder des Teils davon) nähert, auf dem Sie ihn ausführen. Von diesem Punkt an, wenn es schneller sein muss, gewinnen Sie mehr, indem Sie es einfach auf besserer Hardware ausführen. Dies gilt auch für die Parallelisierung; Ein N 2 -Komplexitätsalgorithmus sieht linear aus, wenn er auf N Kernen ausgeführt wird. Wenn Sie also sicher sind, dass Sie die geringere Komplexität für den Typ des Algorithmus erreicht haben, den Sie schreiben, suchen Sie nach Möglichkeiten zum "Teilen und Erobern".
  • Es ist schnell, wenn es schnell genug ist. Wenn Sie die Baugruppe nicht von Hand verpacken, um auf einen bestimmten Chip abzuzielen, gibt es immer etwas zu gewinnen. Wenn Sie jedoch nicht von Hand verpacken möchten, müssen Sie immer bedenken, was der Kunde als "gut genug" bezeichnen würde. Wiederum "ist vorzeitige Optimierung die Wurzel allen Übels"; Wenn Ihr Kunde es schnell genug anruft, sind Sie fertig, bis er nicht mehr glaubt, dass es schnell genug ist.
KeithS
quelle
0

Die einzige Zeit, in der Sie sich frühzeitig Gedanken über die Optimierung machen müssen, ist, wenn Sie wissen, dass Sie mit etwas zu tun haben, das entweder riesig ist oder von dem Sie wissen, dass es sehr oft ausgeführt wird.

Die Definition von "riesig" hängt offensichtlich davon ab, wie Ihre Zielsysteme aussehen.

Loren Pechtel
quelle
0

Ich würde die zweizeilige Version einfach bevorzugen, weil es einfacher ist, mit einem Debugger durchzugehen. Eine Leitung mit mehreren eingebetteten Anrufen macht es schwieriger.

Slapout
quelle