1 Punkte von GN⁺ 2023-12-26 | 1 Kommentare | Auf WhatsApp teilen

Ruby 3.3.0 veröffentlicht

  • Version Ruby 3.3.0 wurde veröffentlicht. Eingeführt werden der neue Parser Prism, der Parser-Generator Lrama, der in reinem Ruby geschriebene JIT-Compiler RJIT sowie insbesondere Leistungsverbesserungen bei YJIT.

Prism-Parser

  • Prism ist ein portabler, fehlertoleranter und wartungsfreundlicher rekursiver Abstiegparser für die Ruby-Sprache und wird als Standard-gem bereitgestellt.
  • Prism ist für den produktiven Einsatz geeignet, wird aktiv gewartet und kann als Ersatz für Ripper verwendet werden.
  • Es steht eine ausführliche Dokumentation zur Verwendung von Prism zur Verfügung.
  • Prism ist sowohl eine C-Bibliothek, die intern in CRuby verwendet wird, als auch ein Ruby-gem, das in allen Tools eingesetzt werden kann, die Ruby-Code parsen müssen.
  • Zu den wichtigsten Methoden der Prism-API gehören Prism.parse(source), Prism.parse_comments(source) und Prism.parse_success?(source).
  • Beiträge sind möglich, indem direkt Pull Requests oder Issues im Prism-Repository eingereicht werden.
  • Wer den Prism-Compiler experimentell nutzen möchte, kann ruby --parser=prism oder RUBYOPT="--parser=prism" verwenden, dies sollte jedoch nur zu Debugging-Zwecken geschehen.

Lrama-Parser-Generator

  • Bison wurde durch den LALR-Parser-Generator Lrama ersetzt.
  • Wer sich dafür interessiert, kann die Zukunftsvision für den Ruby-Parser nachlesen.
  • Zur besseren Wartbarkeit wurde der interne Lrama-Parser durch einen von Racc erzeugten LR-Parser ersetzt.
  • Unterstützung für parametrisierte Regeln (?, *, +), vorgesehen für die Verwendung in Ruby parse.y.

YJIT

  • Es gibt wesentliche Leistungsverbesserungen gegenüber Ruby 3.2.
  • Die Unterstützung für splat- und rest-Argumente wurde verbessert.
  • Für Stack-Operationen der virtuellen Maschine werden Register zugewiesen.
  • Mehr Aufrufe mit optionalen Argumenten werden kompiliert. Auch Exception-Handler werden kompiliert.
  • Nicht unterstützte Aufruftypen und megamorphe Call Sites fallen nicht länger auf den Interpreter zurück.
  • Basis-Methoden wie #blank? in Rails und das spezielle #present? werden inline verarbeitet.
  • Integer#*, Integer#!=, String#!=, String#getbyte, Kernel#block_given?, Kernel#is_a?, Kernel#instance_of?, Module#=== und weitere wurden gezielt optimiert.
  • Die Kompiliergeschwindigkeit ist etwas höher als in Ruby 3.2.
  • In Optcarrot ist es mehr als dreimal schneller als der Interpreter.
  • Der Speicherverbrauch wurde im Vergleich zu Ruby 3.2 deutlich verbessert.
  • Metadaten des kompilierten Codes benötigen wesentlich weniger Speicher.
  • --yjit-call-threshold wird für Anwendungen mit mehr als 40.000 ISEQ automatisch von 30 auf 120 angehoben.
  • --yjit-cold-threshold wurde hinzugefügt und überspringt die Kompilierung ausgekühlter ISEQ.
  • Auf Arm64 wird kompakterer Code erzeugt.
  • Code-GC ist standardmäßig deaktiviert.
  • --yjit-exec-mem-size wird als hartes Limit behandelt, bei dessen Erreichen keine neue Code-Kompilierung mehr erfolgt.
  • Es gibt keinen Performance-Verlust durch Code-GC, und bei Servern, die mit Pitchfork reforken, zeigt sich ein besseres Copy-on-Write-Verhalten.
  • Bei Bedarf kann Code-GC mit --yjit-code-gc aktiviert werden.
  • Mit RubyVM::YJIT.enable kann YJIT zur Laufzeit aktiviert werden.
  • Rails 7.2 wird YJIT auf diese Weise standardmäßig aktivieren.
  • Diese Methode kann genutzt werden, um YJIT erst nach abgeschlossenem Boot-Vorgang der Anwendung zu aktivieren.
  • Wer YJIT beim Start deaktivieren und dennoch andere YJIT-Optionen nutzen möchte, kann --yjit-disable verwenden.
  • Standardmäßig werden mehr YJIT-Statistiken bereitgestellt.
  • yjit_alloc_size und mehrere Metadaten-bezogene Statistiken werden standardmäßig ausgegeben.
  • Die durch --yjit-stats erzeugte Statistik ratio_in_yjit ist in Release-Builds verfügbar. Spezielle Statistik- oder Development-Builds sind nicht mehr erforderlich.
  • Weitere Profiling-Funktionen wurden hinzugefügt.
  • --yjit-perf wurde ergänzt, um Profiling mit Linux perf zu erleichtern.
  • --yjit-trace-exits unterstützt Sampling mit --yjit-trace-exits-sample-rate=N.
  • Umfangreichere Tests und zahlreiche Bugfixes wurden durchgeführt.

RJIT

  • Der in reinem Ruby geschriebene JIT-Compiler RJIT wurde eingeführt und ersetzt MJIT.
  • RJIT wird nur auf Unix-Plattformen mit x86-64-Architektur unterstützt.
  • Anders als MJIT benötigt es zur Laufzeit keinen C-Compiler.
  • RJIT ist ausschließlich für experimentelle Zwecke gedacht.
  • In Produktionsumgebungen sollte weiterhin YJIT verwendet werden.
  • Wer sich für die Entwicklung von Ruby-JIT interessiert, sollte sich den Vortrag von k0kubun am dritten Tag der RubyKaigi ansehen.

M:N-Thread-Scheduler

  • Ein M:N-Thread-Scheduler wurde eingeführt.
  • Da M Ruby-Threads von N nativen Threads (Betriebssystem-Threads) verwaltet werden, sinken die Kosten für Erzeugung und Verwaltung von Threads.
  • Der M:N-Thread-Scheduler kann die Kompatibilität mit C-Erweiterungen beeinträchtigen und ist deshalb im Main Ractor standardmäßig deaktiviert.
  • Mit der Umgebungsvariable RUBY_MN_THREADS=1 kann M:N-Threading im Main Ractor aktiviert werden.
  • In nicht-Main-Ractors ist M:N-Threading immer aktiviert.
  • Die Umgebungsvariable RUBY_MAX_CPU=n setzt die Obergrenze für N, also die maximale Anzahl nativer Threads. Standard ist 8.
  • Da pro Ractor nur ein Ruby-Thread ausgeführt werden kann, nutzen Single-Ractor-Anwendungen (die meisten Anwendungen) nur einen nativen Thread.
  • Zur Unterstützung blockierender Operationen können mehr native Threads als N verwendet werden.

Leistungsverbesserungen

  • defined?(@ivar) wurde mithilfe von Object Shapes optimiert.
  • Namensauflösung wie bei Socket.getaddrinfo kann nun unterbrochen werden, sofern in der jeweiligen Umgebung pthreads verfügbar sind.
  • Es gibt mehrere Performance-Verbesserungen am Garbage Collector.
    • Wenn junge Objekte von alten Objekten referenziert werden, werden sie nicht mehr sofort in die alte Generation hochgestuft, wodurch die Häufigkeit großer GC-Durchläufe stark sinkt.
    • Die neue Tuning-Variable REMEMBERED_WB_UNPROTECTED_OBJECTS_LIMIT_RATIO, die die Anzahl ungeschützter Objekte steuert, welche große GC-Durchläufe auslösen, wurde eingeführt. Der Standardwert liegt bei 0,01 (1 %), wodurch die Häufigkeit großer GC-Durchläufe deutlich sinkt.
    • Für viele Core-Typen, bei denen Write Barriers fehlten, wurde Unterstützung implementiert. Dadurch sinken die Zeiten kleiner GC-Durchläufe und die Häufigkeit großer GC-Durchläufe erheblich.
    • Die meisten Core-Klassen verwenden nun Variable Width Allocation. Das beschleunigt Allokation und Freigabe dieser Klassen, reduziert den Speicherverbrauch und verringert die Heap-Fragmentierung.
    • Schwache Referenzen werden nun vom Garbage Collector unterstützt.

Weitere bemerkenswerte Änderungen

  • IRB erhielt zahlreiche Verbesserungen, darunter eine fortgeschrittene irb:rdbg-Integration, Pager-Unterstützung für die Befehle ls, show_source und show_cmds, genauere und nützlichere Informationen durch ls und show_source sowie experimentelle Autovervollständigung mithilfe von Typanalyse.
  • IRB wurde außerdem umfassend refaktoriert, um künftige Verbesserungen zu erleichtern, und erhielt Dutzende Bugfixes.

Kompatibilitätsprobleme

  • Der Aufruf von it in Blöcken ohne Argumente wird nicht mehr verwendet; in Ruby 3.4 wird er auf den ersten Block-Parameter verweisen.
  • Nicht verwendete Umgebungsvariablen wurden entfernt.

Updates der Standardbibliothek

  • RubyGems und Bundler zeigen eine Warnung an, wenn Nutzer die folgenden gems anfordern, ohne sie zu Gemfile oder gemspec hinzuzufügen. Der Grund ist, dass diese gems in künftigen Ruby-Versionen zu gebündelten gems werden sollen.
  • Die folgenden Standard-gems wurden hinzugefügt oder aktualisiert: prism 0.19.0, RubyGems 3.5.3, abbrev 0.1.2 und viele weitere.
  • Die folgenden gebündelten gems wurden aus Standard-gems hochgestuft oder aktualisiert: racc 1.7.3, minitest 5.20.0 und viele weitere.

Meinung von GN⁺

  • Einführung des Prism-Parsers: Eines der wichtigsten Merkmale von Ruby 3.3.0 ist die Einführung des neuen Prism-Parsers. Er dürfte Ruby-Entwicklern erheblich helfen, indem er Ruby-Code effizienter parst und einen fehlertoleranten sowie leicht wartbaren Parser bereitstellt.
  • Leistungsverbesserungen bei YJIT: Die wesentlichen Performance-Verbesserungen von YJIT werden die Ausführungsgeschwindigkeit von Ruby-Anwendungen deutlich erhöhen. Besonders die Reduzierung des Speicherverbrauchs und die GC-Optimierungen dürften sich positiv auf Performance und Stabilität großer Ruby-Anwendungen auswirken.
  • M:N-Thread-Scheduler: Die Einführung des M:N-Thread-Schedulers hat das Potenzial, die Performance multithreaded Ruby-Anwendungen zu verbessern. Er dürfte den Aufwand für Thread-Verwaltung senken und effizientere Parallelverarbeitung ermöglichen.

1 Kommentare

 
GN⁺ 2023-12-26
Hacker-News-Kommentare
  • Mit dem Erscheinen von Ruby 3.3 legt Ruby, eine Sprache, die viel Wert auf das Glück der Entwickler legt, ihr früheres langsames Image ab und glänzt nun mit hoher Geschwindigkeit.

    • Durch Innovationen wie die YJIT-Technologie, Objektformen und GC-Optimierungen wurde die Performance von Ruby deutlich verbessert.
    • Große Ruby-Nutzer wie Shopify erleben die Performance-Verbesserungen von Ruby 3.3.
    • Ich bin persönlich sehr gespannt auf die Zukunft von Ruby und freue mich darauf, Ruby 3.3 auf den produktiven Websites meiner Kunden einzusetzen.
  • Ruby 3.3 ist das wichtigste und funktionsreichste Release der letzten zehn Jahre, und es ist überraschend, dass es noch vor Python ein JIT veröffentlicht hat.

    • Verschiedene Funktionen wie Prism, Lrama und IRB wurden bereits in früheren Hacker-News-Einreichungen diskutiert.
    • Funktionen wie Ractor, der M:N-Thread-Scheduler, Fibre und Async wurden im Rails-Kontext nicht ausreichend erwähnt, und ich würde gern von den Erfahrungen derjenigen hören, die diese Funktionen in Production nutzen.
  • Hinweis, dass Ruby 3.3 auf Heroku verfügbar ist.

  • Jedes Jahr zu Weihnachten veröffentlicht die Ruby-Sprache ein neues Release.

  • Frage, ob es sich lohnt, Ruby zu lernen, wenn man Python und NodeJS bereits kennt. Ruby wirkt attraktiv, aber auch schwierig.

  • Die Namensauflösung wie bei Socket.getaddrinfo kann blockieren. Jedes Mal, wenn eine Namensauflösung nötig ist, wird ein Worker-pthread erzeugt und getaddrinfo(3) ausgeführt.

    • Frage, ob andere Sprach-Runtimes etwas Ähnliches tun. Das Erzeugen von Threads könnte sich schwergewichtig anfühlen, aber laut Benchmarks ist der Overhead minimal.
  • Prism ist interessant. Frage, ob es Beispiele dafür gibt, Prism als Ruby-Code-Analysetool zu verwenden.

  • Die Umgebungsvariable RUBY_MAX_CPU=n legt die maximale Anzahl nativer Threads fest. Der Standardwert ist 8.

    • Es wird infrage gestellt, ob der Standardwert nicht der Anzahl logischer Kerne entsprechen sollte, wie bei Rusts Tokio und vielen anderen M:N-Runtimes.
  • Suche nach guten Beispielen für die Verwendung von Prism. Enttäuschung darüber, dass auf der Release-Seite außer den „bemerkenswerten APIs“ nicht viel zu finden ist.

  • Erwähnung, dass dies das perfekte Weihnachtsgeschenk sei.