deniz.in

Piyasalar

Hava durumu

Hava durumu yükleniyor

· kaynak Hacker News – Front Page (native)

Rails enum'ları statik metotlar üretecek şekilde yeniden yazıldı, Ruby JIT'leri altında neredeyse 5 kat daha hızlı

railsatscale.com'da yayınlanan bir yazı, define_method ve public_send üzerine kurulu enum metotlarını, runtime'da üretilen statik metotlarla değiştiren bir Rails core pull request'ini ele alıyor; mikrobenchmark throughput'u neredeyse beş katına çıkıyor.

Rails enum'ları statik metotlar üretecek şekilde yeniden yazıldı, Ruby JIT'leri altında neredeyse 5 kat daha hızlı

Runtime'da derlenen Rails enum'ları

railsatscale.com'da "Rails Is a JIT Compiler" başlığıyla yayımlanan bir yazı, framework'ün enum metotları için tanımlama anında daha hızlı kod ürettiği temel bir Rails optimizasyonunu anlatıyor. Yazarın da kabul ettiği gibi başlık bir tuzak, ama içerik gerçek: Rails uygulamanızın küçük parçalarını zaten derliyor ve bu değişiklik bu işi daha iyi yapmasını sağlıyor.

Yazıya göre iş, Sam adında bir geliştiricinin bir Ruby JIT kanalında Rails enum metotlarının Roundhouse'ta optimize edilip edilemeyeceğini sormasıyla başladı; bunun nedeni, üretilen paid? ve refunded? kontrollerini zincirleyen shippable? predicate'ına sahip bir Order modeliydi. Sam'in ilk içgüdüsü enum davranışını Rails'e harici bir build adımında inline etmekti. Yazar buna iki gerekçeyle karşı çıktı: Rails büyük ölçüde dinamik aramalardan uzaklaşıp metotları üretip önbelleğe alma yönünde ilerlemişti ve ZJIT gibi bir Ruby JIT bu metotlara dispatch işlemini yardım almadan halledebilmeliydi.

Enum daha önce ne üretiyordu

enum :status, { pending: 0, paid: 1, ... } gibi bir enum tanımladığınızda Rails, model üzerinde pending? gibi predicate metotları tanımlar. Yazının gösterdiği gibi uygulama, attribute reader üzerinde public_send çağıran ve sonucu bloğun yakaladığı ortamda tutulan bir değerle karşılaştıran bir blokla define_method kullanıyordu.

Bu, çağrı anında hash aramalarını önler çünkü üretilen metot sınıfta önbelleğe alınır; ama yazıya göre hâlâl gereksiz yere dinamiktir. public_send dispatch'i önbelleğe alınmaz ve metot adı statik olarak bilinse bile ekstra mekanizma taşır. Blok ayrıca enum değerini her çağrıda içindeki ortamdan okur, oysa bu değer enum tanımlandığı anda sabitlenmiştir.

Sabitin metoda işlenmesi

Önerilen çözüm, bir string üzerinde module_eval ile sıradan bir metot üretmekti; böylece dispatch doğrudan yapılır ve enum değeri kelimenin tam anlamıyla metodun kaynağına yazılır. Yazarın kendi deyişiyle, Rails iç yapısı hakkında az şey bilen biri olarak, asıl işi yapması için Aaron'u nerd-snipe etti; yazıdaki benchmark çıktıları Aaron'un makinesinden geliyor.

Değişiklik tam olarak tek satırlık olmadı. Rails test paketi, eğik çizgi ve artı işareti içeren timezone etiketleri gibi geçerli Ruby tanımlayıcısı olmayan enum etiketleri içeriyor; gerçek uygulamalar da :Foo gibi büyük harfle başlayan etiketler kullanıyor. Float değerler başka bir sorun ekledi: enum değerleri serileştirilebilir olmalı, ama sonlu olmayan float'lar üretilen kaynak kodda düzgün bir şekilde geri dönüştürülemiyor; bu yüzden uygulamayı bunları hariç tutan bir kontrol içeriyor. Ufuk adında bir katkıcı da üreticinin elle oluşturulmuş string'ler yerine ActiveSupport::CodeGenerator kullanması gerektiğine dikkat çekti.

Rakamlar

Yazı, sıradan bir order.shippable? çağrısını benchmark'lıyor. Rails main üzerinde düz interpreter saniyede 1.721 milyon iterasyon, YJIT 3.568 milyon ve ZJIT 3.812 milyon iterasyon çalıştırıyor. Pull request ile bu rakamlar sırasıyla 3.145 milyon, 16.274 milyon ve 18.509 milyona yükseliyor. Bu, düz interpreter'da kabaca 1.8 kat, YJIT ve ZJIT altında ise yaklaşık 4.6 ile 4.9 kat daha hızlı demek.

Neden önemli

Yazı üç dersle kapanıyor: statik, dinamikten iyidir; public_send yavaştır; ve performans sorunları çoğu zaman, harici bir derleyiciye ya da build adımına başvurmadan, zaten kod üreten kütüphanelerde yukarı doğru (upstream) çözülebilir. Daha geniş çerçevede bu, metaprogramming'in bir derleme biçimi olduğunun kompakt bir gösterimi. Tanımlama anında mümkün olan en statik kodu üreten bir framework, Ruby runtime'larına agresif biçimde optimize edebilecekleri kod teslim eder ve framework'ün kazanımları JIT'in kendi çalışmasıyla rekabet etmek yerine onunla birleşir.

  • #ruby-on-rails
  • #ruby
  • #performance
  • #code-generation
  • #jit

İlgili yazılar