Post by Wanja GaykPost by Marcel MuellerDie Probleme liegen z.B. bei der Speicherverwaltung. Java nutzt den zur
Verfügung gestellten Speicher aus. Wenn er nicht mehr reicht oder die
JVM Langeweile hat, wird aufgeräumt.
Was sich theoretisch gut anhört, ist in der Praxis bei Servern manchmal
ein echtes Problem. Wenn nämlich mehrere JVMs auf einer Maschine laufen
und auch unter Feuer stehen, dann zieht jede JVM über kurz oder lange
den maximal zur Verfügung stehenden Speicher.
Erm. Nein.
Das kommt stark auf dein Programm an.
oder um es ander zu sagen: Wenn das bei dir der Fall ist, dann hast du
wahrscheinlich das Problem, dass deine Objekte zu lange leben, sodass
die VM mit dem Aufräumen nicht mehr hinterher kommt, weil es zu viel in
der Old Generation herum fuhrwerken muss. Will heißen: Alles, was eine
Transaktion ausmacht, sollte wenn möglich mit einer minor collection
verschwinden.
Ajo, man kann natürlich jedes Programm optimieren und dabei auch oft
einen Faktor 10 holen. Voraussetzung man hat beliebig viel Geld und
beliebig viel Zeit. In den anderen Fällen findet man sich in der echten
Welt wieder.
Das eigentliche Problem in Java ist konzeptioneller Natur. Dre GC-Thread
läuft auf niedriger Prio solange der Speicher nicht knapp wird. Wenn man
nun Anwendungen hat, die die Kiste wirklich unter Feuer halten,
schaukeln sich so alle VMs über kurz oder lange auf Xmx hoch. Erst wenn
es wieder Langeweile gibt, ruckelt sich das wieder ein.
Und freier Speicher in der JVM heißt auch noch lange nicht freier
Speicher im OS. Das kommt immer auf die JVM an, wie, wann und ob der
freie Speicher zurück zum OS findet.
Post by Wanja GaykPost by Marcel MuellerDas begrenzt natürlich die
maximale Größe einzelner Transaktionen.
Ich würde eher sagen: Die Dauer die die Transaktionsdaten im Speicher
vorgehalten werden können.
Nein, das ist nicht das Problem. Wenn ich bei obigem Szenario Swappen
vermeiden will, darf ich den Speicher mit den VMs nicht überbuchen. Das
macht den eigentlichen Unterschied. Heißt 7 VMs => Maximale
Transaktionsgröße ~= Speichergröße/7.
Wir hatten da vor einigen Jahren massive Probleme mit einem
Integrationssystem. Eine einzige große Message => Boom!
Mittlerweile wurde sowohl auf der Java-Seite als auch beim Blech
nachgerüstet, so dass das kein operatives Problem mehr ist.
Post by Wanja GaykMartin Thompson ist z.B. bekannt dafür, viel mit Low Latency Anwendungen
zu tun zu haben, beispielsweise war er für LMAX Stock Exchange im
Hochfrequenzhandel für die Software tätig und die können wirklich keine
Millisekunde verschenken.
Ach der Unsinn. Ja, das ist heißes Zeug. Wird Zeit, dass endlich eine
Transaktionssteuer kommt, die solch einen volkswirtschaftlichen Unsinn
eindämmt.
Post by Wanja GaykDie Sache ist, dass man für Low Latency Java-Anwendungen eben nicht ganz
so naiv programmieren kann, wie man es üblicherweise tut, bzw. kann man
mit Java extrem schnell werden, man muss aber einige Sachen beachten.
Ja, "new" vermeiden.
Post by Wanja GaykDa
wäre das "False Sharing" (zwei Daten werden von untershiedlichen Threads
parallel bearbeitet, liegen aber in einem Objekt gebündelt in einer
Cachline - das bedeutet Caches müssen abgeglichen werden und das kann
mal schnell die Performance um eine Größenordnung velangsamen).
Das betrifft restlos alle Sprachen.
Post by Wanja GaykDa wären Zugriffsmuster auf Daten zu beachten, um vom Prefetch des
Speichercontrollers zu profitiere, etc.
Dito. Mit Java hat man aber so gut wie keinen direkten Einfluss auf das
Memory-Layout. Das geht in Grenzen in .NET und ordentlich in C/C++.
Post by Wanja GaykGröße von Daten tun ihr übriges: Binäre Protokolle statt JSON, XML.
Ist halt auch mehr Aufwand beim Debuggen.
Post by Wanja GaykKirk Pepperdine hat einige Präsentationen, in denen er zeigt, wie er
Performance-Problemen auf den Grund geht und das erschreckende daran
erscheint, dass er zu dem Schluss kommt: Erst, wenn du siehst, dass
weder das System, noch thread Contention, noch der Garbage-Collector
dich bremst überleg dir, ob dein Algorithmus richtig ist.
:-)
Ja, die Vorfaktoren werden manchmal etwas zu sehr ignoriert.
Da wird schon bei der Ausbildung genau diese Dummheit gelehrt.
Bei Skalierbarkeit zählt der Algorithmus, bei Geschwindigkeit nicht
unbedingt.
Post by Wanja GaykMan schaut ein paar von den Präsentationen an und fragt sich
willkürlich, wo man selbst etwas in die Richtung optimieren kann.
Schnelle Programme schreiben liegt mir im Blut. Selten, dass mir solche
Faux-Pas passieren. Aber ich komme halt auch noch aus der Zeit, wo man
DB-Kernel auch mal in Assembler geschrieben hat. Damit ging dann auch in
den 80-ern schon search as you type.
Die Ideen von damals taugen zuweilen noch heute. Habe erst kürzlich
einen In-Memory Kern für eine Anwendung geschrieben - allerdings in
.NET, aber das verhält sich in vielen Bereichen ähnlich.
Es gibt zwei Anwendungen mit zumindest ähnlichem Datenmodell und
ähnlicher User- und Transaktionszahl. Unsere läuft auf einer Hasenkiste
mit 2 CPUs bei 5% Last, die des externen Anbieters auf 16 CPUs, die auch
durchaus öfters unter Vollast stehen. Das Ding ist halt klassisch WAMP/LAMP.
Den Haupteffekt hat nicht einmal das InMemory selbst gebracht -
Datenbank-Caches bringen auch eine Menge -, sondern die Deduplication.
Das wiegt auch den zusätzlichen Aufwand dafür dicke auf.
Alle In-Memory Objekte sind immutable - damit ist false sharing raus.
Die Objekte selbst sind Multitons, also pro Primärschlüssel kann immer
nur ein Objekt gleichzeitig im Speicher stehen. Das gilt auch für alle
parallelen Useranfragen. Data Races sind bei Immutable ja kein Problem.
Und last but not least werden redundante Subobjekte dedupliziert, allen
voran Strings. Also anstelle von Wertidentität tritt Referenzidentität.
Dadurch sinkt der Memory Footprint erheblich (ca. Faktor 5) und mit ihm
steigt natürlich die Effizienz der Memory Caches. Des weiteren skaliert
die Sache sogar weniger als linear. Sowohl bei mehr Usern als auch bei
mehr Daten im RAM steigt die Wahrscheinlichkeit für Redundanzen, die der
Deduplikation zum Opfer fallen, weiter an.
Was noch etwas gebracht hat, ist der Einsatz der strukturierten
Wertetypen in .NET. Der Nutzen ist dabei noch nicht einmal unbedingt nur
die Laufzeit, sondern eher die Code-Qualität, Fehlerabhängigkeit und
Wartbarkeit. So können einfache Wertetypen erheblich mehr Typsicherheit
bringen. Ein Primärschlüssel mag technisch gesehen ein String sein, aber
wenn man es mit einem struct Ref<Entität> Typ kapselt, der zur Laufzeit
(also nach dem JIT) keinen zusätzlichen Code erzeugt, wird die Sache
typsicher.
In Java gibt es dazu leider kein Pendant. Da muss man sich zwischen dem
Elementaren Typ mit Performance und der Wrapper-Klasse mit Code
Sicherheit entscheiden.
Das einzige was bei all diesen Techniken wirklich ein Anachronismus ist,
ist SQL bzw. RDBMS.
Marcel