2 puan yazan GN⁺ 2025-02-14 | 1 yorum | WhatsApp'ta paylaş

CRuby'de FFI hızını artırmanın bir yolu var mı?

  • Ruby'de native kod çağırmanız gerektiğinde, mümkün olduğunca çok Ruby kodu yazmak daha iyidir. Çünkü YJIT, Ruby kodunu optimize edebilir ama C kodunu optimize edemez.
  • Native kütüphaneleri çağırırken, işin çoğunu Ruby'de yapıp native fonksiyon çağrıları için basit bir API sunan bir native extension yazmak daha iyidir.
  • FFI, native extension kadar performans sunmaz. Örneğin strlen C fonksiyonunu FFI ile sarmaladığınızda, C extension'a kıyasla performans daha düşüktür.

Benchmark sonuçları

  • String#bytesize'ı doğrudan çağırmak en hızlısıdır ve bunu referans noktası olarak düşünebilirsiniz.
  • C extension üzerinden strlen çağrısı ikinci en hızlıdır; dolaylı olarak String#bytesize çağırmak ise onun ardından gelir.
  • FFI implementasyonu en yavaşıdır. Bu da FFI üzerinden native fonksiyon çağırırken ciddi bir ek yük oluştuğunu gösterir.

Durumu değiştirmek mümkün mü?

  • Chris Seaton'ın fikriyle, dış fonksiyonları çağırmak için JIT kodu üretme olasılığı araştırılıyor.
  • FFI wrapper örneğinde, attach_function çağrısı sırasında wrapper fonksiyon tanımlanırken gerekli makine kodu üretilebilir.

RJIT kullanımı

  • RJIT, Ruby ile yazılmış ve Ruby ile birlikte gelen bir JIT derleyicisidir.
  • 3rd party JIT derleyicilerin Ruby veri yapılarını kolayca eşleyebilmesi için RJIT bir gem olarak ayrıştırıldı.
  • JIT entry function pointer'ı her zaman çalıştırılarak 3rd party JIT'in makine koduna kaydolabilmesi sağlanıyor.

Kavram kanıtı

  • "FJIT" adlı küçük bir kavram kanıtı ile, çalışma anında makine kodu üretilerek dış fonksiyonlar çağrılabiliyor.
  • Benchmark sonuçlarına göre FJIT'in ürettiği makine kodu, C extension'dan daha hızlı ve FFI çağrısından 2 kattan fazla daha hızlı.

Sonuç

  • Bu, C extension ile aynı hızın (hatta daha yüksek hızın) korunurken mümkün olduğunca çok Ruby kodu yazılabileceğini gösteriyor.
  • Ruby, FFI olmadan native kod çağırabilme avantajına sahip olabilir.

Dikkat edilmesi gerekenler

  • Şu anda yalnızca ARM64 platformuyla sınırlı. x86_64 backend'inin eklenmesi gerekiyor.
  • Tüm parametre türleri ve dönüş türleri desteklenmiyor. Yalnızca tek parametre ve tek dönüş değeri işlenebiliyor.
  • Ruby'nin --rjit --rjit-disable bayraklarıyla çalıştırılması gerekiyor. Kokubun'un özelliği uygulandığında bu sorun çözülecek.
  • Şu anda yalnızca Ruby head üzerinde çalışabiliyor.

1 yorum

 
GN⁺ 2025-02-14
Hacker News görüşleri
  • Java Constraint Solver (Timefold) ile CPython arasında fonksiyon çağrıları için yoğun biçimde FFI ile uğraşmak gerekti

    • FFI performans sorunları çoğunlukla ana dil ile yabancı dil arasındaki iletişim için proxy kullanılmasından kaynaklanıyor
    • JNI veya yeni foreign interface kullanılarak yapılan doğrudan FFI çağrıları hızlıdır ve Java metodunu doğrudan çağırmaya benzer hızdadır
    • Ancak CPython ile Java'nın garbage collector'ları birbiriyle iyi uyum sağlamadığından senkronizasyon için özel teknikler gerekiyor
    • JPype veya GraalPy gibi proxy'ler kullanıldığında performans ek yükü oluşuyor; parametreler ve dönüş değerleri dönüştürülmek zorunda kalıyor ve ek FFI çağrıları gerekebiliyor
    • CPython nesneleri Java'ya geçirildiğinde Java, bu CPython nesneleri için bir proxy tutuyor
    • Bu proxy tekrar CPython'a geçirildiğinde, proxy'nin proxy'si oluşturuluyor
    • Sonuç olarak JPype proxy'leri, CPython'u doğrudan FFI ile çağırmaktan %1402 daha yavaş; GraalPy proxy'leri ise %453 daha yavaş
    • Sonunda CPython bytecode'unu Java bytecode'una dönüştürüyor ve kullanılan CPython sınıflarına karşılık gelen Java veri yapıları oluşturuyorlar
    • Bunun sonucunda, proxy kullanmaya kıyasla 100 kat performans artışı elde ediliyor
    • CPython bytecode'unu dönüştürmek veya okumak oldukça kırılgan, dokümantasyonu yetersiz ve VM'in çeşitli tuhaflıkları nedeniyle başka bir bytecode'a doğrudan eşlemek zor
    • Ayrıntılar için blog yazısına bakılabilir: bağlantı
  • Rails At Scale ve byroot'un blogu sayesinde şu sıralar Ruby internalleri ve performansı üzerine derinlemesine tartışmalara ilgi duymak için iyi bir zaman

    • Ruby ve Rails'teki son iyileştirmeler sayesinde Rubyist olmak için güzel bir dönem
  • Dış fonksiyon çağrıları için 3rd party kütüphane çağırmak yerine kodun JIT derlenip derlenemeyeceğine dair bir soru

    • Bunun LuaJIT FFI'nin temel ilkesi olduğundan oldukça eminim: bağlantı
    • LuaJIT'in FFI'sinin çok hızlı olmasının nedeninin bu olduğunu düşünüyorum
  • JVMCI kullanarak anında arm64/amd64 kodu üretip JNI olmadan native kütüphaneleri çağıran bir kütüphane hakkında bilgi: bağlantı

  • "Mümkün olduğunca çok Ruby yazın; özellikle de YJIT Ruby kodunu optimize edebilirken C kodunu optimize edemediği için" görüşü

    • Ruby zaten epey yavaş bir dil değil mi diye merak ediliyor
    • Native tarafa girilecekse, mümkün olduğunca fazla işin native tarafta yapılması isteniyor
  • 10 yıldan uzun süredir Ruby kullanıyorum ve son gelişmeleri görmek gerçekten çok ilginç

    • Heyecan verici
  • Neden JIT derlemeye ihtiyaç duyulduğuna dair soru

    • C ile yazılabiliyorsa, yükleme anında derlenemez mi diye düşünülüyor
  • FFI - Foreign Function Interface, yani Ruby'den C çağırma yöntemi

  • Bunun zaten libffi'nin yaptığı şey olup olmadığına dair soru

  • Sanırım insanların tenderlovemaking.com'a neden gitmediğini anlıyorum