2 poin oleh GN⁺ 2025-02-14 | 1 komentar | Bagikan ke WhatsApp

Adakah cara untuk meningkatkan kecepatan FFI di CRuby?

  • Saat perlu memanggil kode native dari Ruby, sebaiknya tulis sebanyak mungkin kode dalam Ruby. Ini karena YJIT dapat mengoptimalkan kode Ruby, tetapi tidak dapat mengoptimalkan kode C.
  • Saat memanggil library native, sebaiknya lakukan sebagian besar pekerjaan di Ruby dan tulis ekstensi native yang menyediakan API sederhana untuk pemanggilan fungsi native.
  • FFI tidak memberikan performa setara ekstensi native. Misalnya, jika fungsi C strlen dibungkus dengan FFI, performanya lebih rendah dibanding ekstensi C.

Hasil benchmark

  • Memanggil String#bytesize secara langsung adalah yang paling cepat, dan ini bisa dianggap sebagai baseline.
  • Pemanggilan strlen melalui ekstensi C adalah yang tercepat kedua, lalu berikutnya adalah pemanggilan String#bytesize secara tidak langsung.
  • Implementasi FFI adalah yang paling lambat. Ini menunjukkan bahwa ada overhead yang cukup besar saat memanggil fungsi native melalui FFI.

Bisakah mengubah kenyataan ini?

  • Berdasarkan ide dari Chris Seaton, sedang dieksplorasi kemungkinan menghasilkan kode JIT untuk memanggil fungsi eksternal.
  • Pada contoh wrapper FFI, saat pemanggilan attach_function, machine code yang diperlukan dapat dihasilkan pada saat fungsi wrapper didefinisikan.

Memanfaatkan RJIT

  • RJIT adalah compiler JIT yang ditulis dalam Ruby dan disertakan bersama Ruby.
  • RJIT diekstrak menjadi gem agar compiler JIT pihak ketiga dapat dengan mudah memetakan struktur data Ruby.
  • Pointer fungsi entri JIT selalu dijalankan agar JIT pihak ketiga dapat mendaftarkan diri ke machine code.

Proof of concept

  • Melalui proof of concept kecil bernama "FJIT", machine code dapat dihasilkan saat runtime untuk memanggil fungsi eksternal.
  • Hasil benchmark menunjukkan bahwa machine code yang dihasilkan FJIT lebih cepat daripada ekstensi C, dan lebih dari 2x lebih cepat dibanding pemanggilan FFI.

Kesimpulan

  • Ini menunjukkan kemungkinan untuk menulis sebanyak mungkin kode Ruby sambil tetap mempertahankan kecepatan yang sama dengan ekstensi C (atau bahkan lebih cepat).
  • Ruby bisa memiliki keunggulan untuk memanggil kode native tanpa FFI.

Hal yang perlu diperhatikan

  • Saat ini terbatas hanya pada platform ARM64. Backend x86_64 perlu ditambahkan.
  • Tidak menangani semua tipe parameter dan tipe return. Saat ini hanya dapat menangani satu parameter dan satu nilai return.
  • Ruby harus dijalankan dengan flag --rjit --rjit-disable. Ini kemungkinan akan teratasi saat fitur dari Kokubun diterapkan.
  • Saat ini hanya dapat dijalankan pada Ruby head.

1 komentar

 
GN⁺ 2025-02-14
Komentar Hacker News
  • Pernah banyak menangani FFI untuk pemanggilan fungsi antara Java Constraint Solver (Timefold) dan CPython

    • Masalah performa FFI terutama muncul dari penggunaan proxy untuk komunikasi antara bahasa host dan bahasa asing
    • Pemanggilan FFI langsung menggunakan JNI atau foreign interface baru itu cepat, dengan kecepatan yang mirip seperti memanggil metode Java secara langsung
    • Namun, garbage collector CPython dan Java tidak cocok satu sama lain sehingga diperlukan teknik khusus untuk sinkronisasi
    • Jika menggunakan proxy seperti JPype atau GraalPy, muncul overhead performa karena parameter dan nilai balik harus dikonversi, dan bisa terjadi pemanggilan FFI tambahan
    • Jika objek CPython diteruskan ke Java, Java akan memiliki proxy terhadap objek CPython tersebut
    • Jika proxy itu kemudian diteruskan kembali ke CPython, akan dibuat proxy dari proxy
    • Hasilnya, proxy JPype 1402% lebih lambat daripada memanggil CPython langsung lewat FFI, dan proxy GraalPy 453% lebih lambat
    • Pada akhirnya, bytecode CPython dikonversi menjadi bytecode Java, lalu dibuat struktur data Java yang sesuai dengan kelas CPython yang digunakan
    • Hasilnya, didapat peningkatan performa 100 kali lebih cepat dibandingkan menggunakan proxy
    • Mengonversi atau membaca bytecode CPython sangat tidak stabil dan dokumentasinya minim, serta sulit dipetakan langsung ke bytecode lain karena berbagai keanehan VM
    • Detail lebih lanjut bisa dilihat di postingan blog: tautan
  • Berkat Rails At Scale dan blog byroot, sekarang adalah waktu yang tepat untuk tertarik pada diskusi mendalam tentang internal Ruby dan performa

    • Sebagai Rubyist, ini masa yang menyenangkan berkat peningkatan Ruby dan Rails belakangan ini
  • Pertanyaan tentang apakah kode bisa di-JIT-compile untuk pemanggilan fungsi eksternal, alih-alih memanggil library pihak ketiga

    • Yakin ini adalah prinsip dasar FFI di LuaJIT: tautan
    • Sepertinya itu alasan FFI LuaJIT sangat cepat
  • Informasi tentang library yang menggunakan JVMCI untuk menghasilkan kode arm64/amd64 secara langsung guna memanggil native library tanpa JNI: tautan

  • Pendapat bahwa "tulislah Ruby sebanyak mungkin, terutama karena YJIT bisa mengoptimalkan kode Ruby tetapi tidak kode C"

    • Muncul pertanyaan apakah Ruby bukannya bahasa yang cukup lambat
    • Kalau sudah masuk ke native, rasanya ingin memindahkan pekerjaan sebanyak mungkin ke sisi native
  • Sudah menggunakan Ruby lebih dari 10 tahun, dan sangat menarik melihat perkembangan terbaru

    • Menantikannya
  • Pertanyaan mengapa perlu JIT compile

    • Jika bisa ditulis dalam C, muncul pemikiran apakah itu tidak bisa dikompilasi saat load time
  • FFI - Foreign Function Interface, yaitu cara Ruby memanggil C

  • Pertanyaan apakah ini bukankah memang yang dilakukan oleh libffi

  • Sepertinya paham mengapa tidak pergi ke tenderlovemaking.com