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
Komentar Hacker News
Pernah banyak menangani FFI untuk pemanggilan fungsi antara Java Constraint Solver (Timefold) dan CPython
Berkat Rails At Scale dan blog byroot, sekarang adalah waktu yang tepat untuk tertarik pada diskusi mendalam tentang internal Ruby dan performa
Pertanyaan tentang apakah kode bisa di-JIT-compile untuk pemanggilan fungsi eksternal, alih-alih memanggil library pihak ketiga
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"
Sudah menggunakan Ruby lebih dari 10 tahun, dan sangat menarik melihat perkembangan terbaru
Pertanyaan mengapa perlu JIT compile
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