Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Need for Speed: Boost ruby with FFI

Johnlin
September 11, 2015

Need for Speed: Boost ruby with FFI

Let's make ruby the best language in the world by giving it the speed of C. Ruby's goal is maximize programmer happiness, not the happiness of computers. But sometimes we still need to give some sympathy to the poor machine, especially when our API requests timed out. There're multiple ways to speed up Ruby by running C under the hood. Among them, FFI (foreign function interface) is the one that give us the most pleasant experience. With FFI, we can use C libraries without writing a single line of C code. Better yet, we also get cross platform/implementation support for free. We'll start from how FFI works, then calling a simple c function from ruby. Explore more by bridging C structs to ruby class and handing C pointers. Conclude with a FFI example.

Johnlin

September 11, 2015
Tweet

More Decks by Johnlin

Other Decks in Programming

Transcript

  1. C Extension • Write the critical part using Ruby API

    in C • Need compiler on production environment • Not portable between Platforms
  2. Foreign Function Interface • Mounting C function as Ruby method

    • Call functions using C Library libffi • Doesn’t need compiler • Portable between platforms
  3. C Library Structure(ELF) 9/8/2015 https://upload.wikimedia.org/wikipedia/commons/7/77/Elf-layout--en.svg ... .data .rodata .text Program

    header table ELF header Section header table { { https://en.wikipedia.org/wiki/Executable_and_Linkable_Format
  4. Load Library void* dlopen(const char* path, int mode); • load

    a shared library file at path & relocate the address
  5. Function Address λ ~/ nm /usr/lib/libruby.dylib 0000000000027a75 T _InitVM_Enumerator 00000000000088de

    T _Init_Array 000000000012a65e T _Init_BareVM 00000000000164e2 T _Init_Bignum 0000000000037829 T _Init_Binding 000000000001b08e T _Init_Comparable 000000000001be5c T _Init_Complex 000000000013c869 T _Init_Cont 0000000000020721 T _Init_Dir 00000000000032ec T _Init_Encoding 00000000000239a6 T _Init_Enumerable 000000000002906a T _Init_Enumerator 000000000002caa7 T _Init_Exception
  6. Call Function With Address In C typedef int func(void); func

    * f = (func*) 0x12345566; int res = f();
  7. Define Function Signature ffi_status ffi_prep_cif( ffi_cif *cif, ffi_abi abi, unsigned

    int nargs, ffi_type *rtype, ffi_type **atypes); Define the signature of a function. cif stands for Call InterFace
  8. Invoke Function void ffi_call(ffi_cif *cif, void (*fn)(void), void *rvalue, void

    **avalue); • ffi_call(): call the actual function by address, with return address and input parameters
  9. Ruby-FFI • Wrapper of C Library libffi • First appeared

    in Rubinius • Maintained by JRuby team • Available in MRI, JRuby & Rubinius(same interface)
  10. Hello FFI #!/usr/bin/env ruby require 'ffi' module LIBC extend FFI::Library

    ffi_lib FFI::Library::LIBC attach_function :printf, [:string], :int end LIBC.printf("hello FFI\n")
  11. Loading Library • Loading Multiple Libs
 ffi_lib "foo", "bar" •

    Loading all possible names of a Lib
 ffi_lib [“foo", “foo-1.2”]
  12. Attach Function • Function rename
 attach_function :c_print, :printf, [:string], :int

    • Function with variable length
 attach_function :printf, [:string, :varargs], :int

  13. Supported C Types • Integer : singed/unsigned of various length

    • Floating Point: float / double • String: NULL terminated char array. • Pointer: pointer
  14. Define C Structs class Timeval < FFI::Struct layout :tv_sec =>

    :ulong, :tv_usec => :ulong end struct timeval { unsinged long tv_sec; /* seconds since 1/1/1970 */ unsinged long tv_usec;/* and microseconds */ }; C Ruby
  15. Accessing C Struct module LibC extend FFI::Library ffi_lib FFI::Library::LIBC attach_function

    :gettimeofday, [ :pointer, :pointer ], :int end t = Timeval.new LibC.gettimeofday(t.pointer, nil) puts "t.tv_sec=#{t[:tv_sec]} t.tv_usec=#{t[:tv_usec]}" int gettimeofday(struct timeval * tp, void * tzp); C signature Ruby
  16. Define Callback module LibC extend FFI::Library ffi_lib FFI::Library::LIBC callback :qsort_cmp,

    [ :pointer, :pointer ], :int attach_function :qsort, [ :pointer, :ulong, :ulong, :qsort_cmp ], :void end void qsort(void *base, size_t nel, size_t width, int (*compar)(const void *, const void *)); C signature Ruby
  17. Using Callback pointer = FFI::MemoryPointer.new(:int, 2) ary = [ 2,

    1 ] pointer.write_array_of_int32(ary) size_of_int = 4 LibC.qsort(pointer, ary.count, size_of_int) do |p1, p2| i1 = p1.read_int32 i2 = p2.read_int32 i1 < i2 ? -1 : i1 > i2 ? 1 : 0 end p pointer.get_array_of_int32(0, ary.count)
  18. Features • Load C library • Invoke C function •

    Auto convert between Ruby types & C types • Define/Access C Struct • Using block/Proc as callback
  19. Benchmark • Find Eigenvalue & Eigenvector of a matrix •

    Benchmark the runtime of • Ruby Matrix in Stdlib • C Extension with LAPACK • FFI with LAPACK
  20. Take aways • Don’t give up ruby if you need

    speed. • How FFI works under the hood • FFI core features
  21. Q&A

  22. References • Linker & Loaders • Libffi • ruby-ffi Gem

    • Race car Image • x86 call convention