- YJIT ve ZJIT, Ruby 3.x’te Ruby kodunu makine diline dönüştürerek yürütme hızını artıran JIT derleyici yapılarıdır
- YJIT, her fonksiyon veya blok çağrısının sayısını sayar ve belirli bir eşiğe ulaşıldığında ilgili kodu makine diline dönüştürür
- Dönüştürülen kod YJIT blokları içinde saklanır ve her blok, birden fazla YARV komutunu karşılayan ARM64 makine dili komutlarına çevrilir
- Branch Stub kullanarak çalışma anında gerçek veri tiplerini gözlemler ve buna uygun makine dili komutlarını seçerek üretir
- Bu yapı, Ruby’nin çalışma performansını artırma ve dinamik tip işlemeyi verimli hale getirme hedeflerini aynı anda gerçekleştiren temel mekanizmadır
Chapter 4: Ruby’yi makine diline derlemek
Interpreting vs. Compiling Ruby Code
- Orijinal metinde ayrıntı yok
Counting Method and Block Calls
- YJIT, programın fonksiyon ve blok çağrı sayılarını izleyerek hotspot kodu belirler
- Her fonksiyon veya blok için YARV komut dizisinin yanında jit_entry ve jit_entry_calls değerleri saklanır
jit_entry başlangıçta null’dır; daha sonra YJIT tarafından oluşturulan makine dili kodunun işaretçisini saklar
jit_entry_calls her çağrıldığında 1 artar
- Çağrı sayısı eşik değere ulaştığında YJIT ilgili kodu makine diline derler
- Ruby 3.5’te varsayılan eşik küçük programlar için 30 çağrı, büyük uygulamalar için 120 çağrıdır
- Çalıştırma sırasında
--yjit-call-threshold seçeneğiyle değiştirilebilir
- Bu yöntemle YJIT, yalnızca sık çalıştırılan kodu makine diline dönüştürerek verimli bir yürütme yolu sağlar
YJIT Blocks
- YJIT, ürettiği makine dili komutlarını YJIT blokları içinde saklar
- YJIT bloğu, Ruby bloğundan farklıdır ve YARV komutlarının belirli bir bölümünü temsil eder
- Her Ruby fonksiyonu veya bloğu birden fazla YJIT bloğundan oluşur
- Örnek programda blok 30. kez çalıştığında YJIT derlemeye başlar
- İlk YARV komutu olan
getlocal_WC_1 makine diline çevrilerek yeni bir YJIT bloğu oluşturulur
- Ardından
getlocal_WC_0 komutu da derlenip aynı bloğa eklenir
- Figure 4-8’e göre YJIT, M1 işlemcisindeki x1 ve x9 yazmaçlarına değer yükleyen ARM64 komutları üretir
getlocal_WC_1, önceki stack frame’in yerel değişkenini; getlocal_WC_0 ise mevcut stack’teki değişkeni stack’e kaydeder
- Üretilen makine dili komutları aynı işi yerine getirir
YJIT Branch Stubs
- YJIT,
opt_plus komutunu derlerken operand tiplerini bilememe sorunu ile karşılaşır
- Tamsayı, string, kayan nokta gibi tiplere göre gerekli makine dili komutları değişir
- Örneğin tamsayı toplaması
adds komutunu kullanırken, kayan nokta toplaması farklı komutlar gerektirir
- Bunu çözmek için YJIT, önceden analiz etmek yerine çalışma anında gözlemleme yaklaşımını kullanır
- Program çalışırken gerçekten iletilen değerlerin tipini kontrol eder ve buna uygun makine dili üretir
- Bu davranış için Branch Stub kullanılır
- Yeni bir branch için henüz bağlı bir blok yoksa geçici olarak bir stub’a bağlanır
- Daha sonra gerçek tip belirlendiğinde ilgili stub uygun blokla değiştirilir
ZJIT (yalnızca anılıyor)
- İçindekilerde ZJIT ile ilgili bir bölüm yer alsa da, metinde ayrıntılı açıklama yok
Özet
- YJIT, Ruby 3.5’te dinamik tipli bir dilin yürütme verimliliğini artırmak için kullanılan bir JIT derleyicisidir
- Çağrı sayısına dayalı derleme tetikleme, YJIT blok yapısı ve Branch Stub ile çalışma anında tip doğrulama temel unsurlardır
- ARM64 mimarisinde gerçek makine dili komutlarına dönüştürerek Ruby kodunun çalışma hızını artırır
- ZJIT, yeni nesil bir JIT olarak anılsa da metinde ayrıntı verilmez
1 yorum
Hacker News görüşleri
Eskiden MacRuby, LLVM kullanarak macOS üzerinde yerel koda derlenir ve Objective‑C framework’leriyle entegre olurdu
Oldukça hoş bir fikirdi, ama sonunda Apple’ın yönünü Swift’e çevirdiği anlaşılıyor
Yeni sürüm çıkarsa Ruby Under a Microscope kitabını mutlaka satın alıp okumayı düşünüyorum. Ruby’yi hâlâ seviyorum ama onu gerçekten kullanma fırsatım pek olmadı
Şimdi işi başkaları sürdürüyor, ama şu sıralar daha çok DragonRuby’ye (oyun odaklı bir Ruby implementasyonu) odaklanılmış gibi görünüyor
Bilgi için wiki sayfası da var
Yalnız eski API’lerin bazıları artık desteklenmiyor olabilir
VB6 gerçekten çok hızlı geliştirme sağlıyordu ve Direct3D ile ASP Classic’e kadar kullanılabiliyordu
Ruby’nin zarafeti ve geliştirme kolaylığı bana o dönemi hatırlatıyor
Eğer Ruby’de VB6 seviyesinde GUI araçları olsaydı, popülerliği epey farklı olabilirdi
Pat’in projeyi sürdürmeye devam ettiğini görmek gerçekten sevindirici
İlk Ruby Under a Microscope kitabı ve blog yazıları bana büyük ilham vermişti
Yıllar önce Euruko konferansında onunla bizzat tanışmıştım; gerçekten harika biriydi
Ruby Under a Microscope’u ilk okuduğumda gerçekten çok keyif almıştım
Hatta o sayede geçmişte CTF çözümünde de faydalanmıştım
Son zamanlarda Ruby’nin iç implementasyonunu takip etmiyorum ama yeni sürüm çıkarsa kesin almayı düşünüyorum
Bu yazıyı görünce kitabın yeni sürümünü yeniden okumak istedim
Madem Ruby derlemesinden bahsediliyor, Stripe geliştiricilerinin yaptığı Sorbet compiler’ı deneyen oldu mu diye merak ediyorum
Sorbet Compiler’ın açık kaynak yapılmasıyla ilgili yazı
AOT derleme Ruby’de gerçekten çok zor
Sorbet’in yaklaşımını ilginç kılan şey, Ruby’nin tip denetimini temel alarak hızlı yollar oluşturabilmesi
Ben de kişisel bir proje olarak bir Ruby derleyicisi yapıyorum; hokstad.com/compiler ile
writing-a-compiler-in-ruby kaynaklarına bakıyorum
Şu anda RubySpec’i geçmeye odaklandım, ileride tip tabanlı optimizasyonları da denemeyi düşünüyorum
Ruby derlemesiyle doğrudan ilgili değil ama Enterprise Integration with Ruby kitabı, Ruby’yi web dışındaki alanlarda kullanma konusunda bana ciddi içgörü kazandırmıştı
MRuby’yi keşfettiğimden beri, projelerimi ve script’lerimi bağımsız çalıştırılabilir dosyalara dönüştürmenin keyfine kapılmış durumdayım
Ruby Under a Microscope’un hâlâ güncelleniyor olmasına seviniyorum
Ruby’nin iç işleyişini anlamak isteyen herkes için bunun mutlaka okunması gereken bir kitap olduğunu düşünüyorum
YJIT’te bir blok birden çok kez çalıştırıldığında, giriş tiplerine göre derlemenin nasıl takip edildiğini merak ediyordum
Ruby’nin int, float gibi farklı tipleri nasıl ele aldığını öğrenmek istiyorum
Gerçek tip bilgisi gelene kadar derlemeyi erteleyen bir “bekle‑gör” yaklaşımı kullanıyor
Her tip için blokların ayrı sürümlerini tutuyor ve duruma göre uygun olanı çağırıyor
Bu algoritmaya Basic Block Versioning deniyor
Shopify’den Maxime Chevalier‑Boisvert bunu RubyConf 2021 konuşma videosunda güzelce anlatıyor
Yeni JIT motoru ZJIT ise görünüşe göre farklı bir yaklaşım kullanıyor
Dinamik tipli dilleri JIT ile hızlandırmanın bedeli genelde artan bellek kullanımı oluyor
Shopify gibi büyük bir şirket değilseniz bu daha da büyük bir sorun olabilir
Günümüzde bulut instance’ları çekirdek başına yaklaşık 4GiB bellek veriyor, bu yüzden birkaç yüz MB’lık JIT kodu rahatlıkla karşılanabilir
YJIT’in yalnızca fonksiyon çağrı sayısını sayarak hotspot bulması bana biraz basit görünmüştü
JavaScript JIT’lerinde olduğu gibi döngü içindeki ağır işlemleri tespit eden bir mekanizma var mı diye merak ettim
Ruby’nin blok yapısı böyle bir optimizasyona yardımcı olabilir gibi geliyor
JIT bu blokları ayrı birer fonksiyon gibi işleyip döngüleri doğal şekilde optimize edebiliyor
Bu konu bir sonraki bölümde daha derin ele alınacak