Ruby und mehrere Kerne
Sunday, 18. March 2018
Im Laufe der Zeit habe ich einige Performance-Optimierungen an meinen Ruby/Sinatra-Anwendungen vorgenommen. Insbesondere bei pc-kombo, denn dort würde ohne tiefere Optimierungen die Antwortzeiten eher bei Minuten als bei Sekunden liegen. Der wichtigste Baustein dort ist die Optimierung der Datenbankabfragen und Caching via memoist.
Im Hinterkopf hatte ich auch immer die Idee, dass die moderne Hardware mit ihren vielen Kernen gut genutzt werden sollte. Das macht Ruby ja leider nicht automatisch. Ich hatte mit dem parallel-gem gespielt, und ich benutze für Hintergrundaufgaben einen Threadpool. Aber wie effektiv kann das sein, wenn die reguläre MRI-Ruby-Implementierung mit einem globalen Lock (GIL) arbeitet und daher paralleles Ausführen von Code praktisch nicht geht? Selbst wenn der Linux-Scheduler Threads über mehrere Kerne verteilen kann, mehrere Prozesse dafür nicht nötig sind? Reicht das Warten auf IO und alles andere, während dessen kein Ruby-Code ausgeführt wird, um mit MRI trotz GIL die Prozessorkerne auszunutzen?
Um das durch Vergleich zu beantworten habe ich mir andere Ruby-Implementierungen angeschaut. Die beiden bekannten: jRuby und Rubinius. Beide sollten das MRI-Problem beseitigen und damit neue Performaneoptimierungen ermöglichen. Spoiler: Beide funktionierten für mich nicht, bis zum Benchmarken kam ich gar nicht.
jRuby
jRuby ist Ruby auf der JVM, der virtuellen Maschine die ursprünglich für Java genutzt wurde und inzwischen einige andere Sprachen trägt. Der Vorteil davon soll sein, dass jRuby-Programme von den Optimierungen der JVM profitieren, und das schließt Parallelismus mit ein. jRuby war der Anleitung folgend erstaunlich einfach installiert: Java installieren, jRuby herunterladen, entpacken und ausführen. Auch rvm unterstützt jRuby, als ich beim Ausführen dann nicht durchaute wie ich den PATH
und gems verwalten soll löschte ich die manuelle Installation wieder und installierte es damit.
Es scheiterte dann für mich an sqlite3. Das sqlite3-gem nutzt C-Code, und jRuby kann keine C-Module ausführen. Stattdessen würde man wohl jdbc laden, also das machen wie im Java-Land. Dafür müsste ich meinen Datenbankcode umschreiben, und für ein Experiment ungewissen Ausgangs war das keine Option. Das ist wohl weniger problematisch, wenn man activerecord benutzt, dann stellt man das einfach auf den jdbc-Adapter um.
Rubinius
Rubinius hatte ich mir vor einiger Zeit schonmal angeschaut, damals war es nicht mal installierbar. Inzwischen hat das Projekt wohl etwas Zeit darein investiert, tatsächlich Nutzer zu erreichen, die Installation war ein simples rvm install rbx-3
. Aber Rubinius ist trotzdem noch nicht praxistauglich genug.
Erst hing es sich an meiner Benchmark-Klasse auf. Ich hatte mir den Issue-Tracker angeschaut und fand die Interaktionen dort nett genug, um diesen für mich unerklärlichen Bug zu melden. Dort bekam ich dann auch wirklich gute Hilfe, Benchmark wird derzeit durch die Standard-Lib belegt, ich müsste es in ein Modul packen. Gesagt, getan, nächster Fehler. Nun war es das http-accept-gem, mit dem pc-kombo die Lokalisierung umsetzt, das an einer unvollständigen StringScanner-Implementierung starb. Auch diesen Fehler meldete ich.
Ich bin erfreut über die gute Antwortzeit des Projektes, aber es ist für mich unmöglich einzuschätzen, ob Rubinius auch nur annähernd kompatibel genug ist, eine echte Sinatra-Webanwendung mit ihren weiteren gems auszuführen. Auch absolut unklar ist, ob Rubinius überhaupt einen Performancevorteil bringen würde - neben der GIL-losigkeit kamen viel der berichteten positiven Performanceerfahrungen wohl vom JIT-Interpreter, der aber inzwischen herausgenommen wurde. Könnte immer noch Parallelismus ermöglichen und damit meinen Anwendungen nützen, aber klar ist das nicht.
Alternative: Der MRI-Weg
Außer auf Verbesserungen bei Rubinius zu hoffen sehe ich für mich drei Alternativen:
- Ich könnte einfach nichts machen. Seit der letzten Performance-Tuning-Runde ist die Seite wieder schnell. Durch Debugging hatte ich bemerkt, dass das Problem bei den Datenbankabfragen lag, nicht am Prozessor. Da waren blöde Sachen dabei, wie für jede Hardwarekomponente beim Seitenaufbau nochmal in der Datenbank nach Bildern zu suchen. Solange die Seite keinen Besucheransturm erlebt kann das also erstmal so bleiben. Auch wenn es generell doof ist, die Fähigkeiten moderner Prozessoren mit meiner bevorzugten Sprache nicht gut nutzen zu können.
- Ich könnte auf Ruby 3 hoffen. Großes Ziel der Entwicklung sind Performanceverbesserungen, 3x3 war das Stichwort. Bessere Unterstützung von Parallelimus/Concurrency durch neue Operatoren und Konzepte ist auch dabei, selbst die Beseitigung des GILs wurde ins Auge gefasst. Das könnte ich verfolgen und frühzeitig versuchen einzubauen.
- Wenn jetzt etwas geschehen soll könnte ich auch die Architektur der Anwendung ändern. Derzeit ist das ein Monolith, wobei an ein paar Kernstellen ein Threadpool benutzt wird. Ich könnte es aufsplitten: Eine Anwendung zum Aktualisieren der Preise, eine andere zum Zeichnen der Weboberfläche, eine dritte zum Erstellen der Hardwarezusammenstellungen. Herausforderung wäre das Austauschen der Informationen, aber auch dafür gibt es schon Lösungen.
Theoretisch könnte man natürlich auch die Sprache wechseln, aber dafür ist pc-kombo viel zu groß. Trotzdem etwas für den Hinterkopf für Leser, die diesen Artikel lesen um für eine noch zu bauende Anwendung den Einfluss von Ruby auf die Performance einzuschätzen. Wichtig ist dann die Erkenntnis: Sollen mehrere Prozessorkerne genutzt werden, muss das genau geplant werden - Ruby macht das nicht für dich.
Netz - Rettung - Recht am : Wellenreiten 03/2018
Vorschau anzeigen