Hintergrund
Ich habe eine alte (aber großartige) Website erneut besucht, auf der ich lange nicht mehr war - das Alioth Language Shootout ( http://benchmarksgame.alioth.debian.org/ ).
Ich habe vor einigen Jahren mit dem Programmieren in C / C ++ begonnen, arbeite seitdem jedoch aufgrund von Sprachbeschränkungen in den Projekten, an denen ich beteiligt war, fast ausschließlich in Java. Da ich mich nicht an die Zahlen erinnere, wollte ich ungefähr sehen, wie gut Java ist in Bezug auf die Ressourcennutzung gegen C / C ++ abgeschlagen.
Die Ausführungszeiten waren immer noch relativ gut, wobei Java im schlechtesten Fall 4x langsamer lief als C / C ++, aber im Durchschnitt bei 2x (oder darunter). Aufgrund der Art der Implementierung von Java selbst war dies keine Überraschung, und die Performance-Zeit war tatsächlich geringer als erwartet.
Der eigentliche Baustein war die Speicherzuweisung - im schlimmsten Fall hat Java Folgendes zugewiesen:
- satte 52x mehr Speicher als C
- und 25x mehr als C ++.
52x die Erinnerung ... Absolut böse, oder? ... oder ist es? Speicher ist jetzt vergleichsweise billig.
Frage:
Wenn wir nicht in Bezug auf Zielplattformen mit strengen Einschränkungen des Arbeitsspeichers (dh eingebettete Systeme und dergleichen) sprechen, sollte die Speichernutzung heute ein Problem bei der Auswahl einer Allzwecksprache sein?
Ich frage zum Teil, weil ich erwäge, zu Scala als meine Hauptsprache zu migrieren. Ich mag die funktionalen Aspekte sehr, aber soweit ich das sehe, ist es in Bezug auf den Speicher sogar noch teurer als Java. Da der Speicher jedoch von Jahr zu Jahr schneller, billiger und umfangreicher zu werden scheint (es scheint immer schwieriger zu werden, einen Consumer-Laptop ohne mindestens 4 GB DDR3-RAM zu finden), kann nicht argumentiert werden, dass das Ressourcenmanagement immer umfangreicher wird irrelevant im Vergleich zu (möglicherweise in Bezug auf die Implementierung teuren) Hochsprachenfunktionen, die eine schnellere Erstellung besser lesbarer Lösungen ermöglichen?
Antworten:
Die Speicherverwaltung ist äußerst relevant, da sie regelt, wie schnell etwas angezeigt wird, auch wenn das etwas viel Speicher hat. Das beste und kanonischste Beispiel sind AAA-Titel-Spiele wie Call of Duty oder Bioshock. Hierbei handelt es sich im Grunde genommen um Echtzeitanwendungen, die hinsichtlich Optimierung und Nutzung eine enorme Kontrolle erfordern. Es ist nicht die Verwendung an sich, die das Problem darstellt, sondern das Management.
Auf zwei Worte kommt es an: Garbage Collection. Garbage Collection-Algorithmen können zu leichten Performance-Einbußen führen oder sogar dazu, dass die Anwendung für ein oder zwei Sekunden hängen bleibt. Meist harmlos in einer Buchhaltungs-App, aber potenziell ruinös in Bezug auf die Benutzererfahrung in einem Call of Duty-Spiel. In Anwendungen, in denen es auf Zeit ankommt, können Sprachen, die überflüssig werden, sehr problematisch sein. Dies ist zum Beispiel eines der Designziele von Squirrel, das versucht, das Problem, das Lua mit seinem GC hat, zu beheben, indem stattdessen die Referenzzählung verwendet wird.
Ist es eher Kopfschmerzen? Sicher, aber wenn Sie eine präzise Steuerung benötigen, müssen Sie sich damit abfinden.
quelle
Haben Sie verstehen die Zahlen Sie Ihre Frage auf der Basis?
Wenn es große Unterschiede zwischen diesen Java- und C-Programmen gibt, ist dies meist die Standard-JVM-Speicherzuordnung im Vergleich zu den Anforderungen von libc:
Java-Programm 13.996 KB :: C-Programm 320 KB :: Free Pascal 8 KB
Sehen Sie sich die Aufgaben an, für die die Zuweisung von Speicher erforderlich ist (oder verwenden Sie zusätzliche Puffer, um Ergebnisse von Multicore-Programmen zu sammeln):
mandelbrot
Java-Programm 67 , 880 KB :: C-Programm 30 , 444 KB
k-Nukleotid-
Java-Programm 494 , 040 KB :: C-Programm 153 , 452 KB
Reverse-Complement-
Java-Programm 511 , 484 KB :: C-Programm 248 , 632 KB
Regex-DNA-
Java-Programm 557 , 080 KB :: C-Programm 289 , 088 KB
Java-Programm für Binärbäume 506 , 592 KB :: C-Programm 99 , 448 KB
Es hängt davon ab, ob die spezifische Verwendung für Ihren spezifischen Lösungsansatz für die spezifischen Probleme, die Sie lösen müssen, durch die spezifischen Grenzen des verfügbaren Speichers auf der spezifischen Plattform, die verwendet wird, eingeschränkt wird.
quelle
Wie bei allen Dingen ist es ein Kompromiss.
Wenn Sie eine Anwendung erstellen, die auf einem einzelnen Benutzer-Desktop ausgeführt werden soll und von der vernünftigerweise erwartet werden kann, dass sie einen großen Teil des Arbeitsspeichers auf diesem Computer steuert, lohnt es sich möglicherweise, die Speichernutzung für die Implementierungsgeschwindigkeit zu opfern. Wenn Sie auf denselben Computer abzielen, aber ein kleines Dienstprogramm erstellen, das mit einer Reihe anderer speicherhungriger Anwendungen konkurrieren wird, die gleichzeitig ausgeführt werden, sollten Sie bei diesem Kompromiss vorsichtiger vorgehen. Ein Benutzer kann mit einem Spiel gut zurechtkommen, das seinen gesamten Speicher benötigt, wenn es ausgeführt wird (obwohl World Engineer darauf hinweist, dass er Ich mache mir Sorgen, wenn der Müllsammler beschließt, die Aktion regelmäßig anzuhalten, um einen Sweep durchzuführen. Sie sind wahrscheinlich weniger begeistert, wenn der Musikplayer, den sie im Hintergrund laufen lassen, während sie andere Dinge tun, eine Tonne Speicher auffressen und stört ihre Arbeitsfähigkeit. Wenn Sie eine webbasierte Anwendung erstellen, wird durch den auf den Servern verwendeten Arbeitsspeicher die Skalierbarkeit eingeschränkt, sodass Sie mehr Geld für mehr Anwendungsserver ausgeben müssen, um dieselbe Benutzergruppe zu unterstützen. Dies kann erhebliche Auswirkungen auf die Wirtschaftlichkeit des Unternehmens haben. Sie sollten daher sehr vorsichtig sein, wenn Sie diesen Kompromiss eingehen. Jeder Speicher, den Sie auf den Servern verwenden, schränkt Ihre Skalierbarkeit ein und zwingt Sie dazu, mehr Geld für mehr Anwendungsserver auszugeben, um dieselbe Gruppe von Benutzern zu unterstützen. Dies kann erhebliche Auswirkungen auf die Wirtschaftlichkeit des Unternehmens haben. Sie sollten daher sehr vorsichtig sein, wenn Sie diesen Kompromiss eingehen. Jeder Speicher, den Sie auf den Servern verwenden, schränkt Ihre Skalierbarkeit ein und zwingt Sie dazu, mehr Geld für mehr Anwendungsserver auszugeben, um dieselbe Gruppe von Benutzern zu unterstützen. Dies kann erhebliche Auswirkungen auf die Wirtschaftlichkeit des Unternehmens haben. Sie sollten daher sehr vorsichtig sein, wenn Sie diesen Kompromiss eingehen.
quelle
Dies hängt von einer Reihe von Faktoren ab, insbesondere von der Größe, in der Sie arbeiten.
Nehmen wir aus Gründen der Argumentation einen 30-fachen Unterschied im Arbeitsspeicher und einen 2-fachen Unterschied in der CPU-Auslastung an.
Wenn es sich um ein interaktives Programm handelt, das 10 Megabyte Arbeitsspeicher und 1 Millisekunde CPU benötigt, wenn es in C geschrieben wird, ist es ziemlich belanglos - 300 Megabyte Arbeitsspeicher und 2 Millisekunden sind auf einem typischen Desktop normalerweise völlig irrelevant. und es ist unwahrscheinlich, dass dies selbst auf einem Telefon oder Tablet viel bedeutet.
Der Unterschied zwischen etwa der Hälfte der Ressourcen eines Servers und der Notwendigkeit von 15 Servern ist jedoch ein viel größerer Schritt - zumal das Skalieren auf 15 Server wahrscheinlich viel zusätzlichen Aufwand erfordert, um sich zu entwickeln, anstatt weniger. Was die zukünftige Expansion betrifft, deuten dieselben Faktoren, die Sie erwähnen, darauf hin, dass Sie es sind, wenn Ihr Kundenstamm nicht massiv wächst und wenn er jetzt auf einem Server ausgeführt wird kann dies problemlos durch einen neueren Server ersetzen.
Der andere Faktor, den Sie wirklich berücksichtigen müssen, ist der genaue Unterschied in den Entwicklungskosten, den Sie für Ihre spezielle Aufgabe feststellen werden. Im Moment betrachten Sie im Grunde eine Seite einer Gleichung. Um eine gute Vorstellung von Kosten und Nutzen zu erhalten, müssen Sie (offensichtlich genug) sowohl Kosten als auch Nutzen betrachten, und nicht nur einen für sich. Die eigentliche Frage lautet im Grunde: "Ist x größer als y?" - aber Sie können das nicht feststellen, indem Sie nur x betrachten. Sie müssen sich natürlich auch y ansehen.
quelle
Speicherverwaltung ist in der heutigen Welt absolut relevant. Allerdings nicht so, wie Sie es vielleicht erwarten. Selbst in Sprachen, in denen Müll gesammelt wird, müssen Sie sicherstellen, dass Sie kein Referenzleck haben
Sie machen etwas falsch, wenn dies Ihr Code ist:
Die Garbage Collection kann nicht auf magische Weise wissen, dass Sie nie wieder einen Verweis verwenden werden, es sei denn, Sie machen ihn so, dass Sie ihn nicht wieder verwenden können. Auf diese Weise warnen
Cache=null
Sie den Garbage Collector effektiv, dass "Hey, das werde ich nicht können greifen Sie nicht mehr darauf zu. Tun Sie, was Sie wollen. "Es ist komplizierter als das, aber Referenzlecks sind genauso, wenn nicht sogar schädlicher als herkömmliche Speicherlecks.
Es gibt auch Orte, an denen Sie keinen Müllsammler unterbringen können. Beispielsweise ist der ATTiny84 ein Mikrocontroller mit 512 Byte Code-ROM und 32 Byte RAM. Viel Glück! Das ist ein Extrem und würde wahrscheinlich nur in Assembler programmiert werden, aber immer noch. In anderen Fällen verfügen Sie möglicherweise über 1 MB Arbeitsspeicher. Sicher, Sie könnten einen Garbage Collector einsetzen, aber wenn der Prozessor sehr langsam ist (entweder aufgrund von Einschränkungen oder um die Batterie zu schonen), sollten Sie keinen Garbage Collector verwenden, da es zu teuer ist, das zu verfolgen, was ein Programmierer wissen könnte .
Es wird auch erheblich schwieriger, die Garbage Collection zu verwenden, wenn Sie garantierte Antwortzeiten benötigen. Wenn Sie beispielsweise einen Herzmonitor oder Ähnliches haben und
1
an einem Port einen empfangen , müssen Sie sicherstellen, dass Sie innerhalb von 10 ms mit einem geeigneten Signal oder Ähnlichem darauf reagieren können. Wenn der Garbage Collector mitten in Ihrer Antwortroutine einen Pass ausführen muss und es 100 ms dauert, bis er antwortet, ist möglicherweise jemand tot. Die Speicherbereinigung ist sehr schwierig, wenn nicht sogar unmöglich, wenn zeitliche Anforderungen garantiert werden müssen.Selbstverständlich gibt es auch bei moderner Hardware Fälle, in denen Sie diese zusätzlichen 2% der Leistung benötigen, indem Sie sich nicht um den Overhead eines Garbage Collectors kümmern.
quelle
Wie Donald Knuth sagte, ist vorzeitige Optimierung die Wurzel allen Übels. Machen Sie sich keine Sorgen, es sei denn, Sie haben Grund zu der Annahme, dass das Gedächtnis der Engpass sein wird. Und da Moores Gesetz immer noch eine erhöhte Speicherkapazität bietet (auch wenn wir keinen schnelleren Single-Thread-Code erhalten), gibt es allen Grund zu der Annahme, dass der Speicher in Zukunft noch weniger eingeschränkt sein wird als wir sind heute.
Das heißt, wenn Optimierung nicht verfrüht ist, tun Sie es auf jeden Fall. Ich persönlich arbeite gerade an einem Projekt, in dem ich meine Speichernutzung sehr genau verstehe. Ich brauche eine präzise Steuerung, und ein Mülleimer würde mich umbringen. Ich mache dieses Projekt daher in C ++. Aber diese Wahl scheint für mich alle paar Jahre ein Ereignis zu sein. (Hoffentlich werde ich in ein paar Wochen C ++ noch ein paar Jahre nicht mehr berühren.)
quelle
Für Menschen, die mit "Big Data" zu tun haben, ist die Speicherverwaltung immer noch ein großes Problem. Programme in den Bereichen Astronomie, Physik, Bioinformatik, maschinelles Lernen usw. müssen sich alle mit Multi-Gigabyte-Datensätzen befassen, und die Programme werden viel schneller ausgeführt, wenn die relevanten Teile im Speicher gehalten werden können. Sogar das Ausführen auf einem Computer mit 128 GB RAM löst das Problem nicht.
Es gibt auch die Frage, wie Sie die GPU nutzen können, obwohl Sie dies vielleicht als eingebettetes System einstufen würden. Die meiste Mühe bei der Verwendung von CUDA oder OpenCL beruht auf Speicherverwaltungsproblemen beim Übertragen von Daten vom Hauptspeicher in den GPU-Speicher.
quelle
Um ehrlich zu sein, eine Menge Java gibt sich einigen wirklich und sinnlos klassenexplosiven Mustern hin, die nur Leistung und Schweinegedächtnis ermorden, aber ich frage mich, wie viel von diesem Gedächtnis nur die JVM ist, die Sie theoretisch (heh) ausführen lassen gleiche App in mehreren Umgebungen, ohne dass neue vollständig neu geschrieben werden müssen. Aus diesem Grund dreht sich alles um die Frage nach dem Design: "Wie viel Speicherplatz Ihres Benutzers ist Ihnen ein solcher Entwicklungsvorteil wert?"
Dies ist meiner Meinung nach ein absolut lohnender und vernünftiger Kompromiss. Was mich jedoch aufregt, ist die Vorstellung, dass wir, weil moderne PCs so leistungsfähig und der Speicher so billig ist, solche Bedenken und aufgedunsenen Funktionen und den aufgedunsenen Code völlig ignorieren und bei der Auswahl so faul sein können, dass es so aussieht, als ob es eine Menge Zeug wäre Das mache ich jetzt auf einem Windows-PC, dauert genauso lange wie in Windows '95. Im Ernst, Word? Wie viel neuen Mist, den 80% ihrer Benutzer tatsächlich brauchen, hätten sie möglicherweise in 18 Jahren hinzufügen können? Ziemlich sicher, dass wir Rechtschreibprüfung vor Windows hatten, oder? Aber wir sprachen über Erinnerungen, die nicht unbedingt schnell sind, wenn Sie genug davon haben, also schweife ich ab.
Aber wenn Sie die App in 2 Wochen fertig stellen können, um vielleicht ein paar Megabyte mehr als 2 Jahre, um die Version nur für wenige K zu erhalten, ist es natürlich eine Überlegung wert, wie viele Megabytes im Vergleich zu ( Ich vermute) 4-12 Gigs auf dem Computer eines durchschnittlichen Benutzers, bevor ich mich über die Idee lustig mache, so schlampig zu sein.
Aber was hat das mit Scala jenseits der Kompromissfrage zu tun? Nur weil es sich um eine Garbage Collection handelt, heißt das nicht, dass Sie nicht immer versuchen sollten, über den Datenfluss nachzudenken, was sich in Bereichen und Verschlüssen befindet, und ob die Daten herumstehen oder so verwendet werden sollten, wie es sein wird von GC freigegeben, wenn es nicht mehr benötigt wird. Das ist etwas, worüber auch wir JavaScript-Benutzeroberflächen-Entwickler nachdenken mussten und das wir hoffentlich weiter machen werden, wenn wir uns auf andere Problembereiche wie den perfiden Krebs ausbreiten (den Sie alle mit Flash oder Applets hätten töten sollen oder etwas, wenn Sie die Chance dazu hatten). das sind wir.
quelle
Speicherverwaltung (oder -steuerung) ist eigentlich der Hauptgrund, warum ich C und C ++ verwende.
Nicht schnelles Gedächtnis. Wir sehen uns noch eine kleine Anzahl von Registern an, etwa 32 KB Datencache für L1 auf i7, 256 KB für L2 und 2 MB für L3 / Core. Das gesagt:
Speicherauslastung auf einer allgemeinen Ebene, vielleicht nicht. Ein bisschen unpraktisch finde ich, dass ich die Idee eines Notizblocks nicht mag, der beispielsweise 50 Megabyte DRAM und Hunderte Megabyte Festplattenspeicher beansprucht, obwohl ich das zu wenig und reichlich mehr habe. Ich bin schon lange dabei und es fühlt sich für mich einfach komisch und unangenehm an, zu sehen, wie eine so einfache Anwendung relativ viel Speicherplatz für das benötigt, was mit Kilobyte machbar sein sollte. Das heißt, ich könnte vielleicht mit mir selbst leben, wenn mir so etwas begegnet wäre, wenn es immer noch nett und ansprechbar wäre.
Der Grund, warum mir die Speicherverwaltung in meinem Bereich wichtig ist, besteht darin, die Speichernutzung im Allgemeinen nicht so stark zu reduzieren. Hunderte von Megabyte Arbeitsspeicher belasten eine Anwendung nicht unbedingt auf nicht triviale Weise, wenn nicht häufig auf diesen Arbeitsspeicher zugegriffen wird (z. B. nur durch Klicken auf eine Schaltfläche oder eine andere Form der Benutzereingabe, die nur in Ausnahmefällen erfolgt) Es geht um koreanische Starcraft-Spieler, die möglicherweise millionenfach pro Sekunde auf eine Schaltfläche klicken.
Der Grund, warum es in meinem Bereich wichtig ist, dass der Speicher eng und dicht beieinander ist, auf den in diesen kritischen Pfaden sehr häufig zugegriffen wird (z. B. über jeden einzelnen Frame geschleift zu werden). Wir möchten nicht jedes Mal einen Cache-Fehler haben, wenn wir auf eines von Millionen Elementen zugreifen, auf die alle in einer Schleife bei jedem einzelnen Frame zugegriffen werden müssen. Wenn wir den Speicher in großen Blöcken von langsamem auf schnelles Speichern in der Hierarchie verschieben, z. B. 64-Byte-Cache-Zeilen, ist es sehr hilfreich, wenn diese 64-Byte-Blöcke alle relevanten Daten enthalten, wenn wir mehrere Datenelemente in diese 64-Byte-Blöcke einfügen können und wenn unsere Zugriffsmuster so sind, dass wir sie alle verwenden, bevor die Daten gelöscht werden.
Die Daten, auf die häufig zugegriffen wird, für die Millionen Elemente umfassen möglicherweise nur 20 Megabyte, obwohl wir Gigabyte haben. Es macht immer noch einen großen Unterschied, wie viele Frameraten diese Daten bei jedem einzelnen Frame durchlaufen, wenn der Speicher knapp und dicht beieinander ist, um Cache-Ausfälle zu minimieren. Hier ist die Speicherverwaltung / -steuerung von großem Nutzen. Einfaches visuelles Beispiel für eine Kugel mit einigen Millionen Eckpunkten:
Das obige ist tatsächlich langsamer als meine veränderbare Version, da es eine beständige Datenstrukturdarstellung eines Netzes testet, aber abgesehen davon hatte ich Mühe, solche Bildraten auch bei der Hälfte dieser Daten zu erreichen (zugegebenermaßen ist die Hardware seit meinen Kämpfen schneller geworden ), weil ich es nicht geschafft habe, Cache-Ausfälle und den Speicherbedarf für Mesh-Daten zu minimieren. Netze sind einige der schwierigsten Datenstrukturen, mit denen ich mich in diesem Zusammenhang befasst habe, weil sie so viele voneinander abhängige Daten speichern, die synchron bleiben müssen, wie Polygone, Kanten, Scheitelpunkte, so viele Texturabbildungen, wie der Benutzer anfügen möchte, Knochengewichte, Farbkarten, Auswahlsets, Morph-Ziele, Kantengewichte, Polygonmaterialien usw. usw. usw.
Ich habe in den letzten Jahrzehnten eine Reihe von Mesh-Systemen entworfen und implementiert, deren Geschwindigkeit oft sehr proportional zur Speichernutzung war. Obwohl ich mit so viel mehr Speicher arbeite als zu Beginn, sind meine neuen Mesh-Systeme über 10-mal schneller als mein erstes Design (vor fast 20 Jahren) und zu einem großen Teil, weil sie ungefähr 1/10 davon verbrauchen die Erinnerung. Die neueste Version verwendet sogar eine indizierte Komprimierung, um so viele Daten wie möglich zu komprimieren, und trotz des Verarbeitungsaufwands der Dekomprimierung hat die Komprimierung tatsächlich die Leistung verbessert, da wir wiederum so wenig wertvollen schnellen Speicher haben. Ich kann jetzt eine Million Polygonnetze mit Texturkoordinaten, Kantenfalten, Materialzuordnungen usw. zusammen mit einem räumlichen Index in etwa 30 Megabyte anpassen.
Hier ist der veränderbare Prototyp mit über 8 Millionen Vierecken und einem Unterteilungsschema für mehrere Reifen auf einem i3 mit einem GF 8400 (dies war vor einigen Jahren). Es ist schneller als meine unveränderliche Version, wird aber nicht in der Produktion verwendet, da ich die unveränderliche Version für viel einfacher zu warten und die Leistung nicht allzu schlecht finde. Beachten Sie, dass das Drahtgitter keine Facetten, sondern Flecken anzeigt (die Drähte sind tatsächlich Kurven, da sonst das gesamte Netz durchgehend schwarz ist), obwohl alle Punkte in einer Facette vom Pinsel geändert werden.
Auf jeden Fall wollte ich oben nur einiges davon zeigen, um einige konkrete Beispiele und Bereiche zu zeigen, in denen die Speicherverwaltung so hilfreich und hoffentlich auch so ist, dass die Leute nicht glauben, ich spreche nur aus meinem Hintern. Ich neige dazu, ein bisschen irritiert zu werden, wenn Leute sagen, Speicher sei so reichlich vorhanden und billig, weil es sich um langsamen Speicher wie DRAM und Festplatten handelt. Es ist immer noch so klein und wertvoll, wenn es um schnelles Gedächtnis geht, und die Leistung bei wirklich kritischen (dh nicht für alle Fälle üblichen) Pfaden bezieht sich darauf, auf diese kleine Menge an schnellem Gedächtnis zu spielen und es so effektiv wie möglich zu nutzen .
Für diese Art von Dingen ist es sehr hilfreich, mit einer Sprache zu arbeiten, mit der Sie übergeordnete Objekte wie beispielsweise C ++ entwerfen und diese Objekte dennoch in einem oder mehreren zusammenhängenden Arrays speichern können, wobei der Speicher von garantiert wird Alle diese Objekte werden zusammenhängend und ohne unnötigen Speicheraufwand pro Objekt dargestellt (Beispiel: Nicht alle Objekte benötigen Reflektion oder virtuellen Versand). Wenn Sie diese leistungskritischen Bereiche tatsächlich betreten, wird es tatsächlich zu einem Produktivitätsschub, wenn Sie die Speicherkontrolle beispielsweise über das Fummeln mit Objektpools und die Verwendung primitiver Datentypen ausüben, um Objekt-Overhead und GC-Kosten zu vermeiden und den Zugriff auf Speicher häufig zu halten zusammen zusammenhängend.
Daher ist Speicherverwaltung / -kontrolle (oder deren Fehlen) in meinem Fall tatsächlich ein dominierender Grund für die Wahl der Sprache, mit der ich Probleme am produktivsten angehen kann. Ich schreibe definitiv meinen Anteil an Code, der nicht leistungskritisch ist, und ich neige dazu, Lua zu verwenden, das von C aus ziemlich einfach einzubetten ist.
quelle