Có cách nào để cải thiện tốc độ FFI của CRuby không?
- Khi cần gọi mã native từ Ruby, tốt nhất là viết càng nhiều mã Ruby càng tốt. Vì YJIT có thể tối ưu mã Ruby, nhưng không thể tối ưu mã C.
- Khi gọi thư viện native, nên thực hiện phần lớn công việc trong Ruby và viết một native extension cung cấp API đơn giản để gọi các hàm native.
- FFI không mang lại hiệu năng ngang với native extension. Ví dụ, nếu bọc hàm C
strlen bằng FFI thì hiệu năng sẽ kém hơn so với C extension.
Kết quả benchmark
- Gọi trực tiếp
String#bytesize là nhanh nhất và có thể xem như mốc chuẩn.
- Gọi
strlen thông qua C extension là nhanh thứ hai, tiếp theo là gọi gián tiếp String#bytesize.
- Triển khai FFI là chậm nhất. Điều này cho thấy việc gọi hàm native qua FFI tạo ra overhead đáng kể.
Có thể thay đổi thực tế này không?
- Từ ý tưởng của Chris Seaton, bài viết đang khám phá khả năng tạo mã JIT để gọi các hàm bên ngoài.
- Trong ví dụ wrapper FFI, khi gọi
attach_function, có thể tạo ra mã máy cần thiết ngay tại thời điểm định nghĩa hàm wrapper.
Tận dụng RJIT
- RJIT là trình biên dịch JIT được viết bằng Ruby và được cung cấp kèm theo Ruby.
- RJIT được tách ra thành gem để các trình biên dịch JIT bên thứ ba có thể dễ dàng ánh xạ các cấu trúc dữ liệu của Ruby.
- Luôn thực thi con trỏ hàm entry của JIT để JIT bên thứ ba có thể đăng ký vào mã máy.
Chứng minh khái niệm
- Một bản chứng minh khái niệm nhỏ có tên "FJIT" có thể tạo mã máy trong lúc chạy để gọi các hàm bên ngoài.
- Kết quả benchmark cho thấy mã máy do FJIT tạo ra nhanh hơn C extension và nhanh hơn hơn 2 lần so với lời gọi FFI.
Kết luận
- Điều này cho thấy khả năng viết càng nhiều mã Ruby càng tốt trong khi vẫn giữ được tốc độ tương đương C extension (hoặc thậm chí nhanh hơn).
- Ruby có thể có lợi thế là gọi được mã native mà không cần FFI.
Lưu ý
- Hiện chỉ giới hạn trên nền tảng ARM64. Cần bổ sung backend x86_64.
- Chưa xử lý mọi kiểu tham số và kiểu trả về. Hiện chỉ hỗ trợ một tham số và một giá trị trả về.
- Cần chạy Ruby với các cờ
--rjit --rjit-disable. Vấn đề này sẽ được giải quyết khi tính năng của Kokubun được áp dụng.
- Hiện chỉ chạy được trên Ruby head.
1 bình luận
Ý kiến Hacker News
Đã phải xử lý rất nhiều FFI để gọi hàm giữa Java Constraint Solver (Timefold) và CPython
Nhờ blog Rails At Scale và byroot, đây là thời điểm rất tốt để quan tâm đến các cuộc thảo luận chuyên sâu về nội bộ và hiệu năng của Ruby
Câu hỏi về việc liệu có thể JIT-compile mã để gọi hàm ngoài thay vì gọi thư viện bên thứ ba cho các lời gọi hàm ngoài hay không
Thông tin về một thư viện dùng JVMCI để sinh mã arm64/amd64 tại chỗ và gọi thư viện native mà không cần JNI: liên kết
Ý kiến rằng "hãy viết càng nhiều Ruby càng tốt, nhất là vì YJIT có thể tối ưu mã Ruby nhưng không thể tối ưu mã C"
Đã dùng Ruby hơn 10 năm, và việc chứng kiến những tiến bộ gần đây thật sự rất thú vị
Thắc mắc vì sao cần JIT compile
FFI - Foreign Function Interface, tức là cách để Ruby gọi C
Câu hỏi liệu đây chẳng phải chính là điều libffi làm hay sao
Có lẽ tôi hiểu vì sao họ không vào tenderlovemaking.com