Existe uma forma de melhorar a velocidade do FFI no CRuby?
- Quando for necessário chamar código nativo a partir de Ruby, o ideal é escrever o máximo possível em Ruby. Isso porque o YJIT consegue otimizar código Ruby, mas não consegue otimizar código C.
- Ao chamar bibliotecas nativas, é melhor fazer a maior parte do trabalho em Ruby e escrever uma extensão nativa que forneça uma API simples para a chamada de funções nativas.
- O FFI não oferece o mesmo desempenho de uma extensão nativa. Por exemplo, ao encapsular a função C
strlen via FFI, o desempenho fica inferior em comparação com uma extensão C.
Resultados do benchmark
- Chamar
String#bytesize diretamente é o mais rápido, e isso pode ser considerado a linha de base.
- A chamada de
strlen por meio de uma extensão C é a segunda mais rápida, e a chamada indireta de String#bytesize vem logo depois.
- A implementação com FFI é a mais lenta. Isso mostra que há um overhead considerável ao chamar funções nativas via FFI.
Dá para mudar essa realidade?
- A partir de uma ideia de Chris Seaton, está sendo explorada a possibilidade de gerar código JIT para chamar funções externas.
- No exemplo de wrapper com FFI, ao chamar
attach_function, é possível gerar o código de máquina necessário no momento em que a função wrapper é definida.
Uso do RJIT
- O RJIT é um compilador JIT escrito em Ruby e distribuído junto com o Ruby.
- O RJIT foi extraído como gem para permitir que compiladores JIT de terceiros façam o mapeamento das estruturas de dados do Ruby com mais facilidade.
- A ideia é sempre executar o ponteiro da função de entrada do JIT para que JITs de terceiros possam se registrar no código de máquina.
Prova de conceito
- Com uma pequena prova de conceito chamada "FJIT", é possível gerar código de máquina em tempo de execução para chamar funções externas.
- Nos benchmarks, o código de máquina gerado pelo FJIT é mais rápido que uma extensão C e mais de 2 vezes mais rápido que chamadas via FFI.
Conclusão
- Isso mostra a possibilidade de escrever o máximo possível em Ruby mantendo a mesma velocidade de extensões C, ou até uma velocidade maior.
- O Ruby pode passar a ter a vantagem de chamar código nativo sem depender de FFI.
Observações
- No momento, isso está limitado à plataforma ARM64. É necessário adicionar um backend para x86_64.
- Nem todos os tipos de parâmetros e de retorno são tratados. Atualmente, só é possível lidar com um único parâmetro e um único retorno.
- É preciso executar o Ruby com as flags
--rjit --rjit-disable. Isso deve ser resolvido quando a funcionalidade de Kokubun for aplicada.
- Atualmente, isso só pode ser executado no Ruby head.
1 comentários
Comentários do Hacker News
Foi preciso lidar bastante com FFI para chamadas de função entre o Java Constraint Solver (Timefold) e o CPython
Graças ao Rails At Scale e ao blog do byroot, este é um ótimo momento para se interessar por discussões aprofundadas sobre os internals e o desempenho do Ruby
Pergunta sobre se seria possível compilar o código com JIT para chamadas de funções externas, em vez de chamar uma biblioteca de terceiros
Informação sobre uma biblioteca que usa JVMCI para gerar código arm64/amd64 na hora e chamar bibliotecas nativas sem JNI: link
Opinião de que "escreva o máximo possível em Ruby, especialmente porque o YJIT consegue otimizar código Ruby, mas não código C"
Uso Ruby há mais de 10 anos, e é muito interessante ver os avanços recentes
Dúvida sobre por que a compilação JIT é necessária
FFI - Foreign Function Interface, ou seja, a forma de chamar C a partir do Ruby
Pergunta se não é exatamente isso que a libffi faz
Acho que dá para entender por que não foram para o tenderlovemaking.com