¿Hay una forma de mejorar la velocidad de FFI en CRuby?
- Cuando necesitas llamar código nativo desde Ruby, es mejor escribir la mayor cantidad posible de código en Ruby. Esto se debe a que YJIT puede optimizar el código Ruby, pero no puede optimizar código C.
- Al llamar bibliotecas nativas, conviene hacer la mayor parte del trabajo en Ruby y escribir una extensión nativa que ofrezca una API simple para invocar funciones nativas.
- FFI no ofrece el mismo rendimiento que una extensión nativa. Por ejemplo, si envuelves la función C
strlen con FFI, el rendimiento es inferior en comparación con una extensión C.
Resultados del benchmark
- Llamar directamente a
String#bytesize es lo más rápido, y puede considerarse como el punto de referencia.
- La llamada a
strlen mediante una extensión C es la segunda más rápida, y después viene llamar indirectamente a String#bytesize.
- La implementación con FFI es la más lenta. Esto muestra que hay una sobrecarga considerable al invocar funciones nativas a través de FFI.
¿Se puede cambiar esta realidad?
- A partir de una idea de Chris Seaton, se está explorando la posibilidad de generar código JIT para llamar funciones externas.
- En el ejemplo del wrapper FFI, al llamar
attach_function, se puede generar en ese momento el código máquina necesario al definir la función wrapper.
Uso de RJIT
- RJIT es un compilador JIT escrito en Ruby que se incluye junto con Ruby.
- Se extrae RJIT como gem para que compiladores JIT de terceros puedan mapear fácilmente las estructuras de datos de Ruby.
- Siempre se ejecuta el puntero de función de entrada del JIT para permitir que un JIT de terceros se registre en el código máquina.
Prueba de concepto
- Con una pequeña prueba de concepto llamada "FJIT", es posible generar código máquina en tiempo de ejecución para llamar funciones externas.
- Según los benchmarks, el código máquina generado por FJIT es más rápido que una extensión C y más de 2 veces más rápido que una llamada FFI.
Conclusión
- Esto demuestra la posibilidad de escribir la mayor cantidad posible de código en Ruby mientras se mantiene la misma velocidad que una extensión C (o incluso una velocidad mayor).
- Ruby podría tener la ventaja de llamar código nativo sin FFI.
Advertencias
- Por ahora está limitado únicamente a la plataforma ARM64. Es necesario agregar un backend x86_64.
- No maneja todos los tipos de parámetros ni todos los tipos de retorno. Solo puede manejar un único parámetro y un único valor de retorno.
- Es necesario ejecutar Ruby con los flags
--rjit --rjit-disable. Esto probablemente se resolverá cuando se aplique la funcionalidad de Kokubun.
- Por ahora solo puede ejecutarse en Ruby head.
1 comentarios
Comentarios en Hacker News
Tuve que trabajar bastante con FFI para llamadas de funciones entre Java Constraint Solver (Timefold) y CPython
Gracias al blog de Rails At Scale y al de byroot, este es un buen momento para interesarse en discusiones profundas sobre el interior de Ruby y su rendimiento
Pregunta sobre si se puede compilar código con JIT en lugar de llamar una librería de terceros para llamadas a funciones externas
Información sobre una librería que usa JVMCI para generar código arm64/amd64 al vuelo y llamar librerías nativas sin JNI: enlace
Opinión de que "escribe tanto Ruby como sea posible, especialmente porque YJIT puede optimizar código Ruby, pero no código C"
He usado Ruby por más de 10 años y es muy interesante ver los avances recientes
Duda sobre por qué se necesita compilación JIT
FFI - Foreign Function Interface, es decir, una forma de llamar C desde Ruby
Pregunta de si eso no es justamente lo que hace libffi
Creo que ya entiendo por qué no fueron a tenderlovemaking.com