goffing around with ruby #rubyconfph

39
GoFFIng around with Ruby @gautamrege

Upload: gautam-rege

Post on 21-Mar-2017

133 views

Category:

Software


3 download

TRANSCRIPT

Page 1: GoFFIng around with Ruby #RubyConfPH

GoFFIng around with Ruby@gautamrege

Page 2: GoFFIng around with Ruby #RubyConfPH

@joshsoftware

Page 3: GoFFIng around with Ruby #RubyConfPH
Page 4: GoFFIng around with Ruby #RubyConfPH

FFI

Page 5: GoFFIng around with Ruby #RubyConfPH

Foreign Function Interface

*In stdlib since Ruby 1.9.3

Page 6: GoFFIng around with Ruby #RubyConfPH

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

• 3rd party API requests

• Sending newsletters

• Heavy computation

• Fast searches, aggregation.

Page 7: GoFFIng around with Ruby #RubyConfPH

Why Go?• Power of multi-core.

• Goroutines to the rescue

• Language is restricted focussed.

• Programming Ethics

• Compiled

If it compiles, it works

Page 8: GoFFIng around with Ruby #RubyConfPH

What is FFI anyway?• Language interoperability!

• Calling convention.

• libffi.so

• ruby-ffi

• Fiddle (in Stdlib since 1.9.3)

Page 9: GoFFIng around with Ruby #RubyConfPH

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.

Page 10: GoFFIng around with Ruby #RubyConfPH

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)

Page 11: GoFFIng around with Ruby #RubyConfPH

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

Page 12: GoFFIng around with Ruby #RubyConfPH

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)

}}

Page 13: GoFFIng around with Ruby #RubyConfPH

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)

}}

Page 14: GoFFIng around with Ruby #RubyConfPH

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)

}}

Page 15: GoFFIng around with Ruby #RubyConfPH

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)

}}

Page 16: GoFFIng around with Ruby #RubyConfPH

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

Page 17: GoFFIng around with Ruby #RubyConfPH

Calling Go from Ruby

• Build a C library using Go code.

• Build a FFI extension in Ruby

• Call it !

Page 18: GoFFIng around with Ruby #RubyConfPH

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() {}

Page 19: GoFFIng around with Ruby #RubyConfPH

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() {}

Page 20: GoFFIng around with Ruby #RubyConfPH

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

Page 21: GoFFIng around with Ruby #RubyConfPH

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

Page 22: GoFFIng around with Ruby #RubyConfPH

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

Page 23: GoFFIng around with Ruby #RubyConfPH

Practical Example

Page 24: GoFFIng around with Ruby #RubyConfPH

Practical ExampleMake Model Variant

Page 25: GoFFIng around with Ruby #RubyConfPH

Practical ExampleMake Model Variant

Page 26: GoFFIng around with Ruby #RubyConfPH

Practical Example

Page 27: GoFFIng around with Ruby #RubyConfPH

Practical Example

Insurance Vendor

Page 28: GoFFIng around with Ruby #RubyConfPH

Practical Example

Insurance Vendor

68 columns

Page 29: GoFFIng around with Ruby #RubyConfPH

Doing the Math!

18 STATES428 CARS

68 VENDORS

= 523,872 cells

Page 30: GoFFIng around with Ruby #RubyConfPH

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

Calculate DepreciationIndex in ElasticSearch

Page 31: GoFFIng around with Ruby #RubyConfPH

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

Page 32: GoFFIng around with Ruby #RubyConfPH

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

Page 33: GoFFIng around with Ruby #RubyConfPH

Performance Benefits

Page 34: GoFFIng around with Ruby #RubyConfPH

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

Page 35: GoFFIng around with Ruby #RubyConfPH

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

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

Page 36: GoFFIng around with Ruby #RubyConfPH

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

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

ElasticSearch indexing took 48 seconds

Page 37: GoFFIng around with Ruby #RubyConfPH

Complexities of FFI• Memory Management (alloc / free)

• Pointer management

• Fiddle::Pointer

• Fiddle::Closure

Don’t use them unless you HAVE to!

Page 38: GoFFIng around with Ruby #RubyConfPH

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 )

Page 39: GoFFIng around with Ruby #RubyConfPH

@joshsoftware

@gautamrege

http://www.codecuriosity.org