- YJIT और ZJIT Ruby 3.x में Ruby कोड को machine language में बदलकर execution speed बढ़ाने वाली JIT compiler architecture हैं
- YJIT हर function या block call की संख्या गिनता है और तय threshold पर पहुँचने पर उस कोड को machine language में बदल देता है
- बदला हुआ कोड YJIT block में store किया जाता है, और हर block कई YARV instructions को संबंधित ARM64 machine instructions में बदलता है
- Branch Stub का उपयोग करके runtime पर actual data types को observe किया जाता है, और उसी के अनुसार machine instructions को चुनकर generate किया जाता है
- यह architecture Ruby की execution performance और dynamic type handling efficiency दोनों को साथ में हासिल करने का एक core mechanism है
Chapter 4: Ruby को machine language में compile करना
Interpreting vs. Compiling Ruby Code
- मूल लेख में विस्तृत विवरण नहीं है
Method और Block Calls की Counting
- YJIT प्रोग्राम के function और block calls की संख्या track करके hotspot code की पहचान करता है
- हर function या block की YARV instruction sequence के पास jit_entry और jit_entry_calls values store की जाती हैं
jit_entry शुरुआत में null होता है, और बाद में YJIT द्वारा बनाए गए machine code के pointer को store करता है
jit_entry_calls हर call पर 1 से बढ़ता है
- जब call count threshold तक पहुँच जाता है, तब YJIT उस कोड को machine language में compile करता है
- Ruby 3.5 में default threshold छोटे प्रोग्राम के लिए 30 calls और बड़े applications के लिए 120 calls है
- runtime पर
--yjit-call-threshold option से इसे बदला जा सकता है
- इस तरीके से YJIT सिर्फ बार-बार चलने वाले कोड को machine language में बदलकर efficient execution path सुनिश्चित करता है
YJIT Blocks
- YJIT अपने generate किए गए machine instructions को YJIT blocks में store करता है
- YJIT block, Ruby block से अलग है और YARV instructions के किसी हिस्से को represent करता है
- हर Ruby function या block कई YJIT blocks से मिलकर बन सकता है
- उदाहरण प्रोग्राम में block के 30वीं बार execute होने पर YJIT compilation शुरू करता है
- पहला YARV instruction
getlocal_WC_1 machine language में बदलकर नया YJIT block बनाया जाता है
- उसके बाद
getlocal_WC_0 instruction को compile करके उसी block में शामिल किया जाता है
- Figure 4-8 के अनुसार, YJIT ARM64 instructions generate करता है और M1 processor के x1, x9 registers में values load करता है
getlocal_WC_1 पिछले stack frame के local variable को, और getlocal_WC_0 current stack के variable को stack में store करता है
- generate की गई machine instructions वही behavior execute करती हैं
YJIT Branch Stubs
- जब YJIT
opt_plus instruction को compile करता है, तब operand types पहले से पता न होने की समस्या आती है
- integer, string, floating-point जैसे types के हिसाब से अलग machine instructions चाहिए होती हैं
- उदाहरण: integer addition में
adds instruction उपयोग होता है, जबकि floating-point addition के लिए अलग instruction चाहिए
- इसे हल करने के लिए YJIT pre-analysis की जगह runtime observation approach का उपयोग करता है
- प्रोग्राम के चलने के दौरान वास्तव में पास हुए values के type को देखकर उसी के अनुसार machine code generate किया जाता है
- इस behavior के लिए Branch Stub का उपयोग किया जाता है
- जब किसी नए branch से अभी तक कोई linked block नहीं होता, तो उसे अस्थायी रूप से stub से जोड़ा जाता है
- बाद में actual type पता चलने पर उस stub को उपयुक्त block से replace कर दिया जाता है
ZJIT (सिर्फ उल्लेख)
- contents में ZJIT से जुड़ा section शामिल है, लेकिन main text में कोई ठोस विवरण नहीं है
सारांश
- YJIT, Ruby 3.5 में dynamic type language की execution efficiency बढ़ाने वाला JIT compiler है
- call count आधारित compilation trigger, YJIT block structure, और Branch Stub के जरिए runtime type checking इसके मुख्य तत्व हैं
- ARM64 architecture पर वास्तविक machine instructions में बदलकर यह Ruby code की execution speed बढ़ाता है
- ZJIT का उल्लेख अगली पीढ़ी के JIT के रूप में है, लेकिन main text में उसका विवरण नहीं है
1 टिप्पणियां
Hacker News राय
एक समय MacRuby था, जो LLVM का इस्तेमाल करके macOS पर native code में compile होता था और Objective‑C framework के साथ integrate होता था
यह काफ़ी शानदार idea था, लेकिन लगता है कि आखिरकार Apple ने Swift की ओर रुख कर लिया
जब नया version आएगा, तो मैं Ruby Under a Microscope किताब ज़रूर खरीदकर पढ़ूंगा। मुझे अब भी Ruby पसंद है, लेकिन उसे वास्तव में इस्तेमाल करने के मौके ज़्यादा नहीं मिले
अब इसे दूसरे लोग आगे बढ़ा रहे हैं, लेकिन फिलहाल माहौल DragonRuby (game-केंद्रित Ruby implementation) पर ज़्यादा ध्यान देने का लगता है
संदर्भ के लिए wiki दस्तावेज़ भी है
हालांकि पुराने API अब शायद support न किए जाते हों
VB6 में development की speed वाकई बहुत तेज़ थी, और Direct3D से लेकर ASP Classic तक संभाला जा सकता था
Ruby की elegance और development convenience उस दौर की याद दिलाती है
अगर Ruby के पास VB6 स्तर के GUI tools होते, तो शायद उसकी लोकप्रियता काफ़ी अलग होती
Pat को अब भी इस project को आगे बढ़ाते देखना सचमुच अच्छा लगता है
उनकी पहली Ruby Under a Microscope किताब और blog posts ने मुझे बहुत प्रेरणा दी थी
मैं उनसे पहले Euruko conference में मिल भी चुका हूँ, और वे सच में शानदार इंसान थे
जब मैंने पहली बार Ruby Under a Microscope पढ़ी थी, तब बहुत मज़ा आया था
उसी की वजह से मैंने पहले CTF problem solving में भी इसका उपयोग किया था
हाल के वर्षों में मैं Ruby internals को follow नहीं कर पाया, लेकिन नया version आएगा तो ज़रूर खरीदूंगा
यह लेख देखकर फिर से इसका नया संस्करण पढ़ने का मन हो गया
Ruby compile करने की बात चली है, तो सोच रहा हूँ कि क्या किसी ने Stripe developers द्वारा बनाए गए Sorbet compiler को इस्तेमाल किया है
Sorbet Compiler public announcement
Ruby में AOT compilation सचमुच बहुत कठिन है
Sorbet का approach दिलचस्प इसलिए था क्योंकि वह Ruby के type checking के आधार पर fast path बना सकता था
मैं भी एक personal project के रूप में Ruby compiler बना रहा हूँ, और hokstad.com/compiler तथा
writing-a-compiler-in-ruby को संदर्भ के रूप में देख रहा हूँ
अभी मेरा ध्यान RubySpec pass कराने पर है, और बाद में type-based optimization आज़माने का इरादा है
Ruby compilation से सीधे जुड़ा नहीं है, लेकिन Enterprise Integration with Ruby किताब ने web के बाहर के क्षेत्रों में Ruby का उपयोग कैसे किया जा सकता है, इस पर मुझे गहरी समझ दी
MRuby के बारे में जानने के बाद से, मुझे अपने projects और scripts को standalone executable में बदलने में बड़ा मज़ा आने लगा है
यह देखकर खुशी होती है कि Ruby Under a Microscope अब भी update हो रही है
Ruby internals को समझना चाहने वालों के लिए यह ज़रूर पढ़ी जाने वाली किताब है
मैं सोच रहा था कि जब YJIT blocks कई बार चलते हैं, तो वह input types के हिसाब से compilation tracking कैसे करता है
Ruby int, float जैसे अलग-अलग types को कैसे handle करता है, यह जानना चाहता था
यह “wait‑and‑see” approach इस्तेमाल करता है, जिसमें actual type मिलने तक compilation टाल दी जाती है
हर type के लिए block version अलग से manage किए जाते हैं, और ज़रूरत के मुताबिक उन्हें call किया जाता है
इस algorithm को Basic Block Versioning कहा जाता है
Shopify के Maxime Chevalier‑Boisvert ने RubyConf 2021 talk video में इसे अच्छी तरह समझाया है
नया JIT engine ZJIT शायद अलग approach इस्तेमाल करता है
dynamic type languages को JIT से तेज़ बनाना आम तौर पर memory usage बढ़ने की क़ीमत पर आता है
Shopify जैसी बड़ी company न हो तो यह और बड़ा मसला हो सकता है
आजकल cloud instances अक्सर प्रति core लगभग 4GiB memory देते हैं, इसलिए कुछ सौ MB के JIT code को आराम से संभाला जा सकता है
YJIT का सिर्फ function call count गिनकर hotspots ढूँढना मुझे थोड़ा सरल लगा
मैं सोच रहा था कि क्या JavaScript JIT की तरह loop के भीतर भारी operations को detect करने की कोई सुविधा नहीं है
Ruby की block structure शायद ऐसे optimization में मदद कर सकती है
इसलिए JIT block को अलग function की तरह treat करके loops को स्वाभाविक रूप से optimize कर सकता है
इस हिस्से को अगले chapter में और गहराई से कवर किया जाएगा