goffing around with ruby #rubyconfph

Post on 21-Mar-2017

133 Views

Category:

Software

3 Downloads

Preview:

Click to see full reader

TRANSCRIPT

GoFFIng around with Ruby@gautamrege

@joshsoftware

FFI

Foreign Function Interface

*In stdlib since Ruby 1.9.3

Why do we need FFI?• Resque / Sidekiq / delayed_job

• 3rd party API requests

• Sending newsletters

• Heavy computation

• Fast searches, aggregation.

Why Go?• Power of multi-core.

• Goroutines to the rescue

• Language is restricted focussed.

• Programming Ethics

• Compiled

If it compiles, it works

What is FFI anyway?• Language interoperability!

• Calling convention.

• libffi.so

• ruby-ffi

• Fiddle (in Stdlib since 1.9.3)

FFI or Good ol’ C-ext?• FFI Easier to write and maintain.

• Gems are now easier to install - don’t need build dependencies.

• Portable code

• No more “Building native extensions…” while installing gems.

Simple Ruby Codedef race(legs, benchmark) tids = [] legs.times do |i| tids << Thread.new(i) do |leg| array = (1..benchmark).map { |i| i } p "Done leg #{leg} benchmark #{benchmark}" end end tids.each { |t| t.join }end

race(4, 50_000_000)

Benchmark - Ruby$ time ruby run1.rb Done leg 1 benchmark 50000000 Done leg 0 benchmark 50000000 Done leg 3 benchmark 50000000 Done leg 2 benchmark 50000000

real 0m18.242s user 0m16.626s sys 0m1.292s

Simple Go Codepackage main

import "fmt"

func race(leg int, benchmark int) {arr := []int{}for i := 0; i < benchmark; i++ {arr = append(arr, i)

}fmt.Println("Done leg:", leg)

}

func main() {for i := 0; i < 4; i++ {race(i, 50000000)

}}

Simple Go Codepackage main

import "fmt"

func race(leg int, benchmark int) {arr := []int{}for i := 0; i < benchmark; i++ {arr = append(arr, i)

}fmt.Println("Done leg:", leg)

}

func main() {for i := 0; i < 4; i++ {race(i, 50000000)

}}

Simple Go Codepackage main

import "fmt"

func race(leg int, benchmark int) {arr := []int{}for i := 0; i < benchmark; i++ {arr = append(arr, i)

}fmt.Println("Done leg:", leg)

}

func main() {for i := 0; i < 4; i++ {race(i, 50000000)

}}

Simple Go Codepackage main

import "fmt"

func race(leg int, benchmark int) {arr := []int{}for i := 0; i < benchmark; i++ {arr = append(arr, i)

}fmt.Println("Done leg:", leg)

}

func main() {for i := 0; i < 4; i++ {race(i, 50000000)

}}

Benchmark - Go$ time go run run1.go Done leg: 0 Done leg: 1 Done leg: 2 Done leg: 3

real 0m2.774s user 0m2.564s sys 0m0.929s

Calling Go from Ruby

• Build a C library using Go code.

• Build a FFI extension in Ruby

• Call it !

c-shared Go librarypackage main

import ("C""fmt"

)

func race(leg int, benchmark int) {arr := []int{}for i := 0; i < benchmark; i++ {

arr = append(arr, i)}fmt.Println("Done leg:", leg)

}

//export churnfunc churn(leg C.int, benchmark C.int) {

for i := 0; i < int(leg); i++ {race(i, int(benchmark))

}}

func main() {}

c-shared Go librarypackage main

import ("C""fmt"

)

func race(leg int, benchmark int) {arr := []int{}for i := 0; i < benchmark; i++ {

arr = append(arr, i)}fmt.Println("Done leg:", leg)

}

//export churnfunc churn(leg C.int, benchmark C.int) {

for i := 0; i < int(leg); i++ {race(i, int(benchmark))

}}

func main() {}

c-shared Go librarypackage main

import ("C""fmt"

)

func race(leg int, benchmark int) {arr := []int{}for i := 0; i < benchmark; i++ {

arr = append(arr, i)}fmt.Println("Done leg:", leg)

}

//export churnfunc churn(leg C.int, benchmark C.int) {

for i := 0; i < int(leg); i++ {race(i, int(benchmark))

}}

func main() {}

$ go build -o librun.so -buildmode=c-shared run2.go

Ruby FFI extensionrequire 'fiddle'

module Fun def self.race(leg, benchmark) librun = Fiddle.dlopen(‘./librun.so')

race = Fiddle::Function.new( librun['churn'], [Fiddle::TYPE_INT, Fiddle::TYPE_INT], Fiddle::TYPE_VOID )

race.call(leg, benchmark) endend

Call it! require './fun'Fun.race(4, 50_000_000)

$ time ruby run2.rb Done leg: 0 Benchmark 50000000 Done leg: 1 Benchmark 50000000 Done leg: 2 Benchmark 50000000 Done leg: 3 Benchmark 50000000

real 0m2.776s user 0m2.719s sys 0m0.969s

Practical Example

Practical ExampleMake Model Variant

Practical ExampleMake Model Variant

Practical Example

Practical Example

Insurance Vendor

Practical Example

Insurance Vendor

68 columns

Doing the Math!

18 STATES428 CARS

68 VENDORS

= 523,872 cells

If that was not enough …For each of the 523,872 cells

Calculate DepreciationIndex in ElasticSearch

for cmake, v := range segments {for model, j := range v {for submodel, k := range j {for insurance, segment := range k {wg.Add(1)go func(cmake string, model string, submodel string, insurance string, segment string) {

defer wg.Done()// data := es_data{...}// prepare_es_data(...)es_add_depreciation(&data)

}(cmake, model, submodel, insurance, segment)}

}}

}

Practical Example

About 350,000 go-routines

for cmake, v := range segments {for model, j := range v {for submodel, k := range j {for insurance, segment := range k {wg.Add(1)go func(cmake string, model string, submodel string, insurance string, segment string) {

defer wg.Done()// data := es_data{...}// prepare_es_data(...)es_add_depreciation(&data)

}(cmake, model, submodel, insurance, segment)}

}}

}

Practical Example

About 350,000 go-routines

Performance Benefits

Performance Benefits• Sidekiq: 2 GB memory & ~18 minutes

Performance Benefits• Sidekiq: 2 GB memory & ~18 minutes

• Go FFI: 135 MB memory & 1 minute 13 seconds.

Performance Benefits• Sidekiq: 2 GB memory & ~18 minutes

• Go FFI: 135 MB memory & 1 minute 13 seconds.

ElasticSearch indexing took 48 seconds

Complexities of FFI• Memory Management (alloc / free)

• Pointer management

• Fiddle::Pointer

• Fiddle::Closure

Don’t use them unless you HAVE to!

Resources• Fiddle http://ruby-doc.org/stdlib-2.0.0/libdoc/fiddle/rdoc/

Fiddle.html

• FFI Core Concepts https://github.com/ffi/ffi/wiki/Core-Concepts

• Github: ffi/ffi

• Effective Go ( https://golang.org/doc/effective_go.html )

• A Tour of Go ( https://tour.golang.org/welcome/1 )

@joshsoftware

@gautamrege

http://www.codecuriosity.org

top related