YJIT - Yet Another Ruby JIT
YJIT ist ein leichtgewichtiger, minimalistischer Ruby JIT, der in CRuby integriert ist. Er kompiliert Code verzögert (lazy) mittels einer Basic Block Versioning (BBV) Architektur. YJIT wird derzeit für macOS, Linux und BSD auf x86-64 und arm64/aarch64 CPUs unterstützt. Dieses Projekt ist Open Source und unterliegt derselben Lizenz wie CRuby.
Wenn Sie YJIT in der Produktion verwenden, teilen Sie uns bitte Ihre Erfolgsgeschichten mit!
Wenn Sie mehr über den Ansatz erfahren möchten, finden Sie hier einige Konferenzvorträge und Veröffentlichungen
-
MPLR 2023 Vortrag: Evaluating YJIT’s Performance in a Production Context: A Pragmatic Approach
-
RubyKaigi 2023 Keynote: Optimizing YJIT’s Performance, from Inception to Production
-
RubyKaigi 2023 Keynote: Fitting Rust YJIT into CRuby
-
RubyKaigi 2022 Keynote: Stories from developing YJIT
-
RubyKaigi 2022 Vortrag: Building a Lightweight IR and Backend for YJIT
-
RubyKaigi 2021 Vortrag: YJIT: Building a New JIT Compiler Inside CRuby
-
Blogbeitrag: YJIT: Building a New JIT Compiler Inside CRuby
-
MPLR 2023 Papier: Evaluating YJIT’s Performance in a Production Context: A Pragmatic Approach
-
VMIL 2021 Papier: YJIT: A Basic Block Versioning JIT Compiler for CRuby
-
MoreVMs 2021 Vortrag: YJIT: Building a New JIT Compiler Inside CRuby
-
ECOOP 2016 Vortrag: Interprocedural Type Specialization of JavaScript Programs Without Type Analysis
-
ECOOP 2016 Papier: Interprocedural Type Specialization of JavaScript Programs Without Type Analysis
-
ECOOP 2015 Vortrag: Simple and Effective Type Check Removal through Lazy Basic Block Versioning
-
ECOOP 2015 Papier: Simple and Effective Type Check Removal through Lazy Basic Block Versioning
Um YJIT in Ihren Publikationen zu zitieren, zitieren Sie bitte das MPLR 2023 Papier
@inproceedings{yjit_mplr_2023,
author = {Chevalier-Boisvert, Maxime and Kokubun, Takashi and Gibbs, Noah and Wu, Si Xing (Alan) and Patterson, Aaron and Issroff, Jemma},
title = {Evaluating YJIT’s Performance in a Production Context: A Pragmatic Approach},
year = {2023},
isbn = {9798400703805},
publisher = {Association for Computing Machinery},
address = {New York, NY, USA},
url = {https://doi.org/10.1145/3617651.3622982},
doi = {10.1145/3617651.3622982},
booktitle = {Proceedings of the 20th ACM SIGPLAN International Conference on Managed Programming Languages and Runtimes},
pages = {20–33},
numpages = {14},
keywords = {dynamically typed, optimization, just-in-time, virtual machine, ruby, compiler, bytecode},
location = {Cascais, Portugal},
series = {MPLR 2023}
}
Aktuelle Einschränkungen
YJIT ist möglicherweise nicht für bestimmte Anwendungen geeignet. Es unterstützt derzeit nur macOS, Linux und BSD auf x86-64 und arm64/aarch64 CPUs. YJIT benötigt mehr Speicher als der Ruby-Interpreter, da der JIT-Compiler Maschinencode im Speicher generieren und zusätzliche Zustandsinformationen pflegen muss. Sie können die Menge des zugewiesenen ausführbaren Speichers über die Kommandozeilenoptionen von YJIT ändern.
Installation
Voraussetzungen
Sie müssen Folgendes installieren:
-
Alle üblichen Build-Tools für Ruby. Siehe Ruby erstellen
-
Der Rust-Compiler
rustc-
Die Rust-Version muss >= 1.58.0 sein.
-
-
Optional, nur wenn Sie im Entwicklungs-/Debug-Modus bauen möchten: Rusts
cargo
Wenn Sie keine Codeänderungen an YJIT selbst vornehmen möchten, empfehlen wir, rustc über den Paketmanager Ihres Betriebssystems zu beziehen, da dieser wahrscheinlich dieselben Zulieferer wiederverwendet, die auch die C-Toolchain bereitstellen.
Wenn Sie den Rust-Code von YJIT ändern werden, empfehlen wir die offizielle Installationsmethode für Rust. Rust bietet auch erstklassige Unterstützung für viele Quellcode-Editoren.
YJIT erstellen
Beginnen Sie mit dem Klonen des ruby/ruby Repositorys
git clone https://github.com/ruby/ruby yjit cd yjit
Das YJIT ruby Binary kann entweder mit GCC oder Clang erstellt werden. Es kann entweder im Entwicklungs-(Debug-)Modus oder im Release-Modus erstellt werden. Für maximale Leistung kompilieren Sie YJIT im Release-Modus mit GCC. Detailliertere Bauanleitungen finden Sie in der Ruby README.
# Configure in release mode for maximum performance, build and install ./autogen.sh ./configure --enable-yjit --prefix=$HOME/.rubies/ruby-yjit --disable-install-doc make -j && make install
oder
# Configure in lower-performance dev (debug) mode for development, build and install ./autogen.sh ./configure --enable-yjit=dev --prefix=$HOME/.rubies/ruby-yjit --disable-install-doc make -j && make install
Der Entwicklungsmodus enthält erweiterte YJIT-Statistiken, kann aber langsam sein. Nur für Statistiken können Sie im Statistikmodus konfigurieren.
# Configure in extended-stats mode without slow runtime checks, build and install ./autogen.sh ./configure --enable-yjit=stats --prefix=$HOME/.rubies/ruby-yjit --disable-install-doc make -j && make install
Unter macOS müssen Sie möglicherweise angeben, wo einige Bibliotheken zu finden sind.
# Install dependencies brew install openssl libyaml # Configure in dev (debug) mode for development, build and install ./autogen.sh ./configure --enable-yjit=dev --prefix=$HOME/.rubies/ruby-yjit --disable-install-doc --with-opt-dir="$(brew --prefix openssl):$(brew --prefix readline):$(brew --prefix libyaml)" make -j && make install
Typischerweise wählt configure den Standard-C-Compiler. Um den C-Compiler anzugeben, verwenden Sie
# Choosing a specific c compiler export CC=/path/to/my/chosen/c/compiler
bevor Sie ./configure ausführen.
Sie können testen, ob YJIT korrekt funktioniert, indem Sie ausführen:
# Quick tests found in /bootstraptest make btest # Complete set of tests make -j test-all
Verwendung
Beispiele
Sobald YJIT erstellt ist, können Sie entweder ./miniruby aus Ihrem Build-Verzeichnis verwenden oder mit dem chruby-Tool zur YJIT-Version von ruby wechseln.
chruby ruby-yjit ruby myscript.rb
Sie können Statistiken zur Kompilierung und Ausführung ausgeben, indem Sie YJIT mit der Kommandozeilenoption --yjit-stats ausführen.
./miniruby --yjit-stats myscript.rb
Sie können sehen, was YJIT kompiliert hat, indem Sie YJIT mit der Kommandozeilenoption --yjit-log ausführen.
./miniruby --yjit-log myscript.rb
Der für eine bestimmte Methode generierte Maschinencode kann durch Hinzufügen von puts RubyVM::YJIT.disasm(method(:method_name)) zu einem Ruby-Skript ausgegeben werden. Beachten Sie, dass kein Code generiert wird, wenn die Methode nicht kompiliert ist.
Kommandozeilenoptionen
YJIT unterstützt alle Kommandozeilenoptionen, die von Upstream CRuby unterstützt werden, fügt aber auch einige YJIT-spezifische Optionen hinzu.
-
--yjit: YJIT aktivieren (standardmäßig deaktiviert) -
--yjit-mem-size=N: weiche Grenze für die Speichernutzung von YJIT in MiB (Standard: 128). Versucht,code_region_size + yjit_alloc_sizezu begrenzen. -
--yjit-exec-mem-size=N: harte Grenze für ausführbare Speicherblöcke in MiB. Begrenztcode_region_size. -
--yjit-call-threshold=N: Anzahl der Aufrufe, nach denen YJIT beginnt, eine Funktion zu kompilieren. Der Standardwert ist 30 und wird dann auf 120 erhöht, wenn die Anzahl der ISEQs im Prozess 40.000 erreicht. -
--yjit-cold-threshold=N: Anzahl globaler Aufrufe, nach denen eine ISEQ als kalt betrachtet und nicht kompiliert wird. Niedrigere Werte bedeuten, dass weniger Code kompiliert wird (Standard 200K). -
--yjit-stats: Statistiken nach der Ausführung eines Programms ausgeben (verursacht Laufzeitkosten). -
--yjit-stats=quiet: Statistiken während der Ausführung eines Programms sammeln, aber nicht ausgeben. Statistiken sind überRubyVM::YJIT.runtime_statszugänglich. (verursacht Laufzeitkosten). -
--yjit-log[=file|dir]: alle Kompilierungsereignisse in die angegebene Datei oder Verzeichnis protokollieren. Wenn kein Name angegeben wird, werden die letzten 1024 Protokolleinträge beim Beenden der Anwendung nach stderr ausgegeben. -
--yjit-log=quiet: einen zirkulären Puffer von kürzlichen YJIT-Kompilierungen sammeln. Die Kompilierungsprotokolleinträge sind überRubyVM::YJIT.logzugänglich, und alte Einträge werden verworfen, wenn der Puffer nicht schnell geleert wird. (verursacht Laufzeitkosten). -
--yjit-disable: YJIT trotz anderer--yjit*Flags deaktivieren, um es mitRubyVM::YJIT.enableverzögert zu aktivieren. -
--yjit-code-gc: Code-GCaktivieren (standardmäßig deaktiviert ab Ruby 3.3). Dies führt dazu, dass der gesamte Maschinencode verworfen wird, wenn die Grenze für ausführbaren Speicher erreicht ist, was bedeutet, dass die JIT-Kompilierung dann neu beginnt. Dies kann Ihnen erlauben, eine niedrigere Grenze für ausführbaren Speicher zu verwenden, kann aber zu einem leichten Leistungsabfall führen, wenn die Grenze erreicht ist. -
--yjit-perf: Frame-Pointer und Profiling mit demperf-Tool aktivieren. -
--yjit-trace-exits: einenMarshal-Dump von Backtraces aller Exits erzeugen. Aktiviert automatisch--yjit-stats. -
--yjit-trace-exits=COUNTER: einenMarshal-Dump von Backtraces eines gezählten oder eines Fallback-Exits erzeugen. Aktiviert automatisch--yjit-stats. -
--yjit-trace-exits-sample-rate=N: Exit-Positionen nur bei jedem N-ten Vorkommen verfolgen. Aktiviert automatisch--yjit-trace-exits.
Beachten Sie, dass es auch eine Umgebungsvariable RUBY_YJIT_ENABLE gibt, mit der YJIT aktiviert werden kann. Dies kann für einige Bereitstellungsskripte nützlich sein, bei denen die Angabe einer zusätzlichen Kommandozeilenoption für Ruby nicht praktikabel ist.
Sie können YJIT auch zur Laufzeit mit RubyVM::YJIT.enable aktivieren. Dies ermöglicht es Ihnen, YJIT zu aktivieren, nachdem Ihre Anwendung mit dem Booten fertig ist, was es ermöglicht, den Initialisierungscode nicht zu kompilieren.
Sie können überprüfen, ob YJIT aktiviert ist, indem Sie RubyVM::YJIT.enabled? verwenden oder indem Sie überprüfen, ob ruby --yjit -v die Zeichenkette +YJIT enthält.
ruby --yjit -v ruby 3.3.0dev (2023-01-31T15:11:10Z master 2a0bf269c9) +YJIT dev [x86_64-darwin22] ruby --yjit -e "p RubyVM::YJIT.enabled?" true ruby -e "RubyVM::YJIT.enable; p RubyVM::YJIT.enabled?" true
Benchmarking
Wir haben eine Reihe von Benchmarks gesammelt und ein einfaches Benchmarking-Framework im Repository yjit-bench implementiert. Dieses Benchmarking-Framework ist darauf ausgelegt, die Skalierung der CPU-Frequenz zu deaktivieren, Prozessaffinitäten einzustellen und die zufällige Adressraumzuordnung zu deaktivieren, um die Varianz zwischen den Benchmark-Läufen so gering wie möglich zu halten.
Leistungstipps für Produktionsbereitstellungen
Obwohl die YJIT-Optionen standardmäßig so eingestellt sind, dass sie unserer Meinung nach für die meisten Workloads gut funktionieren, sind sie möglicherweise nicht die beste Konfiguration für Ihre Anwendung. Dieser Abschnitt enthält Tipps zur Verbesserung der YJIT-Leistung, falls YJIT Ihre Anwendung in der Produktion nicht beschleunigt.
Erhöhen von –yjit-mem-size
Der Wert --yjit-mem-size kann verwendet werden, um die maximale Speichermenge festzulegen, die YJIT verwenden darf. Dies entspricht der Summe von RubyVM::YJIT.runtime_stats[:code_region_size] und RubyVM::YJIT.runtime_stats[:yjit_alloc_size]. Das Erhöhen des Werts von --yjit-mem-size bedeutet, dass mehr Code von YJIT optimiert werden kann, auf Kosten einer höheren Speichernutzung.
Wenn Sie Ruby mit --yjit-stats starten, z. B. über die Umgebungsvariable RUBYOPT=--yjit-stats, zeigt RubyVM::YJIT.runtime_stats[:ratio_in_yjit] den Prozentsatz der von YJIT ausgeführten YARV-Instruktionen im Vergleich zum CRuby-Interpreter an. Idealerweise sollte ratio_in_yjit so hoch wie 99 % sein, und das Erhöhen von --yjit-mem-size hilft oft, ratio_in_yjit zu verbessern.
Worker so lange wie möglich laufen lassen
Es ist hilfreich, denselben Code so oft wie möglich aufzurufen, bevor ein Prozess neu gestartet wird. Wenn ein Prozess zu häufig beendet wird, können die für die Kompilierung von Methoden aufgewendeten Zeit die durch die Kompilierung erzielten Geschwindigkeitssteigerungen überwiegen.
Sie sollten die Anzahl der von jedem Prozess bedienten Anfragen überwachen. Wenn Sie Worker-Prozesse periodisch beenden, z. B. mit unicorn-worker-killer oder puma_worker_killer, sollten Sie die Häufigkeit des Beendens reduzieren oder das Limit erhöhen.
Reduzierung des YJIT-Speicherverbrauchs
YJIT reserviert Speicher für JIT-Code und Metadaten. Die Aktivierung von YJIT führt im Allgemeinen zu einem höheren Speicherverbrauch. Dieser Abschnitt enthält Tipps zur Minimierung des YJIT-Speicherverbrauchs, falls dieser Ihre Kapazität übersteigt.
Verringern von –yjit-mem-size
YJIT verwendet Speicher für kompilierten Code und Metadaten. Sie können die maximale Speichermenge, die YJIT verwenden kann, ändern, indem Sie eine andere Kommandozeilenoption --yjit-mem-size angeben. Der Standardwert ist derzeit 128. Wenn Sie diesen Wert ändern, sollten Sie RubyVM::YJIT.runtime_stats[:ratio_in_yjit] wie oben erklärt überwachen.
YJIT verzögert aktivieren
Wenn Sie YJIT über die Optionen --yjit oder RUBY_YJIT_ENABLE=1 aktivieren, kann YJIT Code kompilieren, der nur während des Bootens der Anwendung verwendet wird. RubyVM::YJIT.enable ermöglicht es Ihnen, YJIT aus Ruby-Code heraus zu aktivieren, und Sie können dies nach der Initialisierung Ihrer Anwendung aufrufen, z. B. im after_fork Hook von Unicorn. Wenn Sie YJIT-Optionen (--yjit-*) verwenden, startet YJIT standardmäßig beim Booten, aber --yjit-disable ermöglicht es Ihnen, Ruby im YJIT-deaktivierten Modus zu starten und gleichzeitig YJIT-Tuning-Optionen zu übergeben.
Codeoptimierungstipps
Dieser Abschnitt enthält Tipps zum Schreiben von Ruby-Code, der auf YJIT so schnell wie möglich ausgeführt wird. Einige dieser Ratschläge basieren auf aktuellen Einschränkungen von YJIT, während andere Ratschläge allgemein anwendbar sind. Es wird wahrscheinlich nicht praktikabel sein, diese Tipps überall in Ihrer Codebasis anzuwenden. Sie sollten idealerweise damit beginnen, Ihre Anwendung mit einem Tool wie stackprof zu profilieren, um festzustellen, welche Methoden den Großteil der Ausführungszeit ausmachen. Sie können dann die spezifischen Methoden refaktorieren, die die größten Anteile der Ausführungszeit ausmachen. Wir empfehlen nicht, Ihre gesamte Codebasis basierend auf den aktuellen Einschränkungen von YJIT zu ändern.
-
Vermeiden Sie die Verwendung von
OpenStruct -
Vermeiden Sie die Neudefinition grundlegender Integer-Operationen (d. h. +, -, <, >, etc.)
-
Vermeiden Sie die Neudefinition der Bedeutung von
nil, Gleichheit usw. -
Vermeiden Sie die Objekterzeugung in den heißen Teilen Ihres Codes.
-
Minimieren Sie indirekte Ebenen.
-
Vermeiden Sie Wrapper-Klassen, wenn möglich (z. B. eine Klasse, die nur einen Ruby-Hash umschließt).
-
Vermeiden Sie Methoden, die nur eine andere Methode aufrufen.
-
Ruby-Methodenaufrufe sind teuer. Vermeiden Sie Dinge wie Methoden, die nur einen Wert aus einem Hash zurückgeben.
-
Versuchen Sie, Code so zu schreiben, dass dieselben Variablen und Methodenargumente immer denselben Typ haben.
-
Vermeiden Sie die Verwendung von
TracePoint, da dies dazu führen kann, dass YJIT Code deoptimiert. -
Vermeiden Sie die Verwendung von
binding, da dies dazu führen kann, dass YJIT Code deoptimiert.
Sie können auch die Kommandozeilenoption --yjit-stats verwenden, um zu sehen, welche Bytecodes YJIT zum Beenden veranlassen, und Ihren Code refaktorieren, um die Verwendung dieser Anweisungen in den heißesten Methoden Ihres Codes zu vermeiden.
Andere Statistiken
Wenn Sie ruby mit --yjit-stats ausführen, verfolgt und gibt YJIT Leistungsinformationen in RubyVM::YJIT.runtime_stats zurück.
$ RUBYOPT="--yjit-stats" irb
irb(main):001:0> RubyVM::YJIT.runtime_stats
=>
{:inline_code_size=>340745,
:outlined_code_size=>297664,
:all_stats=>true,
:yjit_insns_count=>1547816,
:send_callsite_not_simple=>7267,
:send_kw_splat=>7,
:send_ivar_set_method=>72,
...
Einige der Zähler umfassen:
-
:yjit_insns_count- wie viele Ruby-Bytecode-Anweisungen ausgeführt wurden. -
:binding_allocations- Anzahl der zugewiesenen Bindings. -
:binding_set- Anzahl der über ein Binding gesetzten Variablen. -
:code_gc_count- Anzahl der Garbage Collections von kompiliertem Code seit Prozessstart. -
:vm_insns_count- Anzahl der vom Ruby-Interpreter ausgeführten Anweisungen. -
:compiled_iseq_count- Anzahl der kompilierten Bytecode-Sequenzen. -
:inline_code_size- Größe in Bytes von kompilierten YJIT-Blöcken. -
:outline_code_size- Größe in Bytes von YJIT-Fehlerbehandlungs-Kompilierungscode. -
:side_exit_count- Anzahl der zur Laufzeit erfolgten Side-Exits. -
:total_exit_count- Anzahl der zur Laufzeit erfolgten Exits, einschließlich Side-Exits. -
:avg_len_in_yjit- durchschnittliche Anzahl von Anweisungen in kompilierten Blöcken, bevor zu Interpreter zurückgesprungen wird.
Zähler, die mit "exit_" beginnen, zeigen die Gründe für einen Side-Exit von YJIT-Code (Rücksprung zum Interpreter) an.
Namen von Leistungsparametern sind nicht garantiert, dass sie zwischen Ruby-Versionen gleich bleiben. Wenn Sie neugierig sind, was jeder Zähler bedeutet, ist es am besten, den Quellcode danach zu durchsuchen – aber er kann sich in einer späteren Ruby-Version ändern.
Der nach einem --yjit-stats-Lauf ausgegebene Text enthält weitere Informationen, die anders benannt sein können als die Informationen in RubyVM::YJIT.runtime_stats.
Mitwirkung
Wir begrüßen Open-Source-Beiträge. Sie können gerne neue Issues eröffnen, um Fehler zu melden oder einfach Fragen zu stellen. Vorschläge zur Verbesserung dieser Readme-Datei für neue Mitwirkende sind sehr willkommen.
Fehlerbehebungen und Fehlerberichte sind für uns sehr wertvoll. Wenn Sie einen Fehler in YJIT finden, ist es sehr wahrscheinlich, dass niemand ihn zuvor gemeldet hat oder dass wir keine gute Reproduktionsmöglichkeit dafür haben. Bitte eröffnen Sie ein Issue und geben Sie so viele Informationen wie möglich über Ihre Konfiguration und eine Beschreibung an, wie Sie auf das Problem gestoßen sind. Listen Sie die Befehle auf, die Sie zum Ausführen von YJIT verwendet haben, damit wir das Problem leicht reproduzieren und untersuchen können. Wenn Sie ein kleines Programm erstellen können, das den Fehler reproduziert, um uns bei der Nachverfolgung zu helfen, ist das sehr willkommen.
Wenn Sie einen großen Patch zu YJIT beitragen möchten, empfehlen wir, ein Issue oder eine Diskussion im Repository Shopify/ruby zu eröffnen, damit wir eine aktive Diskussion führen können. Ein häufiges Problem ist, dass manchmal Leute große Pull-Requests an Open-Source-Projekte senden, ohne vorherige Kommunikation, und wir müssen sie ablehnen, weil die implementierte Arbeit nicht zum Design des Projekts passt. Wir möchten Ihnen Zeit und Frustration ersparen, also nehmen Sie bitte Kontakt auf, damit wir eine produktive Diskussion darüber führen können, wie Sie Patches beisteuern können, die wir in YJIT aufnehmen möchten.
Organisation des Quellcodes
Der Quellcode von YJIT ist aufgeteilt in:
-
yjit.c: Code, den YJIT zur Schnittstelle mit dem Rest von CRuby verwendet.
-
yjit.h: C-Definitionen, die YJIT dem Rest von CRuby zur Verfügung stellt. -
yjit.rb: Das Ruby-Modul
YJIT, das Ruby zur Verfügung gestellt wird. -
yjit/src/asm/*: In-Memory-Assembler, den wir zur Generierung von Maschinencode verwenden. -
yjit/src/codegen.rs: Logik zur Übersetzung von Ruby-Bytecode in Maschinencode. -
yjit/src/core.rb: Logik für Basic Block Versioning, Kernstruktur von YJIT. -
yjit/src/stats.rs: Erfassung von Laufzeitstatistiken. -
yjit/src/options.rs: Handhabung von Kommandozeilenoptionen. -
yjit/src/cruby.rs: C-Bindungen, die manuell für die Rust-Codebasis verfügbar gemacht werden. -
yjit/bindgen/src/main.rs: C-Bindungen, die über Bindgen für die Rust-Codebasis verfügbar gemacht werden.
Der Kern der Interpreterlogik von CRuby befindet sich in:
-
insns.def: Definiert die Bytecode-Anweisungen von Ruby (wird zuvm.inckompiliert). -
vm_insnshelper.c: Logik, die von den Bytecode-Anweisungen von Ruby verwendet wird. -
vm_exec.c: Ruby-Interpreter-Schleife.
Generierung von C-Bindungen mit Bindgen
Um C-Funktionen für die Rust-Codebasis verfügbar zu machen, müssen Sie C-Bindungen generieren.
CC=clang ./configure --enable-yjit=dev make -j yjit-bindgen
Dies verwendet die Bindgen-Tools, um yjit/src/cruby_bindings.inc.rs basierend auf den in yjit/bindgen/src/main.rs aufgeführten Bindungen zu generieren/aktualisieren. Vermeiden Sie die manuelle Bearbeitung dieser Datei, da sie zu einem späteren Zeitpunkt automatisch neu generiert werden könnte. Wenn Sie manuell C-Bindungen hinzufügen müssen, fügen Sie sie stattdessen zu yjit/cruby.rs hinzu.
Coding & Debugging Protips
Es gibt mehrere Test-Suiten:
-
make btest(siehe/bootstraptest) -
make test-all -
make test-spec -
make checkführt alle obigen Tests aus. -
make yjit-checkführt schnelle Checks durch, um zu sehen, ob YJIT korrekt funktioniert.
Die Tests können parallel wie folgt ausgeführt werden:
make -j test-all RUN_OPTS="--yjit-call-threshold=1"
Oder ein-threaded wie folgt, um leichter zu identifizieren, welcher spezifische Test fehlschlägt:
make test-all TESTOPTS=--verbose RUN_OPTS="--yjit-call-threshold=1"
Um eine einzelne Testdatei mit test-all auszuführen:
make test-all TESTS='test/-ext-/marshal/test_usrmarshal.rb' RUNRUBYOPT=--debugger=lldb RUN_OPTS="--yjit-call-threshold=1"
Es ist auch möglich, Tests nach Namen zu filtern, um einen einzelnen Test auszuführen:
make test-all TESTS='-n /test_float_plus/' RUN_OPTS="--yjit-call-threshold=1"
Sie können auch einen bestimmten Test in btest ausführen:
make btest BTESTS=bootstraptest/test_ractor.rb RUN_OPTS="--yjit-call-threshold=1"
Es gibt Verknüpfungen zum Ausführen/Debuggen Ihres eigenen Tests/Reproduktionsfalls in test.rb.
make run # runs ./miniruby test.rb make lldb # launches ./miniruby test.rb in lldb
Sie können die Intel-Syntax für die Disassemblierung in LLDB verwenden, um sie mit der Disassemblierung von YJIT konsistent zu halten.
echo "settings set target.x86-disassembly-flavor intel" >> ~/.lldbinit
x86 YJIT auf Apples Rosetta ausführen
Zu Entwicklungszwecken ist es möglich, x86 YJIT auf einem Apple M1 über Rosetta auszuführen. Grundlegende Anleitungen finden Sie unten, aber es gibt einige Einschränkungen, die weiter unten aufgeführt sind.
Installieren Sie zuerst Rosetta.
$ softwareupdate --install-rosetta
Jetzt kann jeder Befehl über das Kommandozeilen-Tool arch mit Rosetta ausgeführt werden.
Starten Sie dann Ihre Shell in einer x86-Umgebung.
$ arch -x86_64 zsh
Sie können Ihre aktuelle Architektur mit dem Befehl arch überprüfen.
$ arch -x86_64 zsh $ arch i386
Möglicherweise müssen Sie das Standardziel für rustc auf x86-64 setzen, z. B.
$ rustup default stable-x86_64-apple-darwin
Installieren Sie währenddessen in Ihrer i386-Shell Cargo und Homebrew, und legen Sie dann los!
Rosetta-Einschränkungen
-
Sie müssen eine Version von Homebrew für jede Architektur installieren.
-
Cargo wird standardmäßig unter $HOME/.cargo installiert, und ich kenne keine gute Möglichkeit, die Architektur nach der Installation zu ändern.
Wenn Sie die Fish-Shell verwenden, können Sie diesen Link lesen, um Informationen zu erhalten, wie Sie die Entwicklungsumgebung einfacher gestalten können.
Profiling mit Linux perf
--yjit-perf ermöglicht Ihnen, JIT-kompilierte Methoden zusammen mit anderen nativen Funktionen mit Linux perf zu profilieren. Wenn Sie Ruby mit perf record ausführen, sucht perf nach /tmp/perf-{pid}.map, um Symbole im JIT-Code aufzulösen, und diese Option ermöglicht es YJIT, Methodensymbole in diese Datei zu schreiben und Frame-Pointer zu aktivieren.
Aufrufgraph
Hier ist ein Beispiel, wie diese Option mit Firefox Profiler verwendet werden kann (siehe auch: Profiling mit Linux perf).
# Compile the interpreter with frame pointers enabled ./configure --enable-yjit --prefix=$HOME/.rubies/ruby-yjit --disable-install-doc cflags=-fno-omit-frame-pointer make -j && make install # [Optional] Allow running perf without sudo echo 0 | sudo tee /proc/sys/kernel/kptr_restrict echo -1 | sudo tee /proc/sys/kernel/perf_event_paranoid # Profile Ruby with --yjit-perf cd ../yjit-bench PERF="record --call-graph fp" ruby --yjit-perf -Iharness-perf benchmarks/liquid-render/benchmark.rb # View results on Firefox Profiler https://profiler.firefox.com. # Create /tmp/test.perf as below and upload it using "Load a profile from file". perf script --fields +pid > /tmp/test.perf
YJIT Codegen
Sie können auch die Anzahl der Zyklen profilieren, die für den von jeder YJIT-Funktion generierten Code verbraucht werden.
# Install perf apt-get install linux-tools-common linux-tools-generic linux-tools-`uname -r` # [Optional] Allow running perf without sudo echo 0 | sudo tee /proc/sys/kernel/kptr_restrict echo -1 | sudo tee /proc/sys/kernel/perf_event_paranoid # Profile Ruby with --yjit-perf=codegen cd ../yjit-bench PERF=record ruby --yjit-perf=codegen -Iharness-perf benchmarks/lobsters/benchmark.rb # Aggregate results perf script > /tmp/perf.txt ../ruby/misc/yjit_perf.py /tmp/perf.txt
Perf mit Python-Unterstützung erstellen
Die obigen Anweisungen funktionieren für die meisten Leute gut, aber Sie könnten auch eine praktische perf script -s-Schnittstelle verwenden, wenn Sie perf aus dem Quellcode erstellen.
# Build perf from source for Python support sudo apt-get install libpython3-dev python3-pip flex libtraceevent-dev \ libelf-dev libunwind-dev libaudit-dev libslang2-dev libdw-dev git clone --depth=1 https://github.com/torvalds/linux cd linux/tools/perf make make install # Aggregate results perf script -s ../ruby/misc/yjit_perf.py