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

RedDotRubyConf

Sponsored · Your Podcast. Everywhere. Effortlessly. Share. Educate. Inspire. Entertain. You do you. We'll handle the rest.

 RedDotRubyConf

Avatar for Aaron Patterson

Aaron Patterson

June 07, 2013
Tweet

More Decks by Aaron Patterson

Other Decks in Technology

Transcript

  1. Radius at Latitude R = radius of earth a =

    equatorial radius b = polar radius l = geodetic latitude
  2. Driving a Car class Automatic def put_in_drive; end def gas;

    end end def drive(car) car.put_in_drive car.gas end car = Automatic.new drive(car)
  3. Manual Car class Manual def press_clutch; end def put_in_gear; end

    def release_clutch; end def gas; end end def drive(car) car.press_clutch car.put_in_gear car.release_clutch car.gas end car = Manual.new drive car
  4. Polymorphic class Automatic def go put_in_drive gas end end class

    Manual def go press_clutch put_in_gear release_clutch gas end end
  5. rm Conditions def drive car if car.is_a? Automatic car.put_in_drive car.gas

    else car.press_clutch car.put_in_gear car.release_clutch car.gas end end
  6. Callback Example class Hoge include ActiveSupport::Callbacks define_callbacks :save set_callback :save,

    :record set_callback :save, :record1 def record; puts __method__; end def record1; puts __method__; end end f = Hoge.new f.run_callbacks :save
  7. Variations class Foo include ActiveSupport::Callbacks define_callbacks :save set_callback :save, :record

    set_callback :save, -> { p "lambda1" } set_callback :save, ->(o) { p "lambda2" } set_callback :save, ->(*a) { p "lambda3" } set_callback :save, "puts 'hello'" set_callback :save, SomeClass end call method call lambda eval call “before”
  8. Variations, :if class Foo set_callback :save, :record, if: :foo set_callback

    :save, :record, if: -> { p "lambda1" } set_callback :save, :record, if: ->(o) { p "lambda1" } set_callback :save, :record, if: ->(*a) { p "lambda1" } set_callback :save, :record, if: "true" set_callback :save, :record, if: SomeClass end call method call lambda eval call “before”
  9. Variations, :unless class Foo set_callback :save, :record, unless: :foo set_callback

    :save, :record, unless: -> { p "lambda1" } set_callback :save, :record, unless: ->(o) { p "lambda1" } set_callback :save, :record, unless: ->(*a) { p "lambda1" } set_callback :save, :record, unless: "true" set_callback :save, :record, unless: SomeClass end call method lambda eval call “before”
  10. Variations, location class Foo set_callback :save, :before, -> { "lambda"

    } set_callback :save, :after, -> { "lambda" } set_callback :save, :around, -> { "lambda" } end
  11. Count Methods class Foo include ActiveSupport::Callbacks define_callbacks :save x =

    instance_methods.length 100.times do set_callback :save, -> { "lambda1" } end y = instance_methods.length - x p NEW_METHODS: y end
  12. Count Methods f = Foo.new x = Foo.instance_methods.length f.run_callbacks :save

    y = Foo.instance_methods.length - x p NEW_METHODS: y
  13. eval based when :before <<-RUBY_EVAL if !halted && #{@compiled_options} result

    = result = #{@source} halted = (#{chain.config[:terminator]}) if halted halted_callback_hook(#{@raw_filter}) end end #{code} RUBY_EVAL when :after
  14. when :before <<-RUBY_EVAL if !halted && #{@compiled_options} result = result

    = #{@source} halted = (#{chain.config[:terminator]}) if halted halted_callback_hook(#{@raw_filter}) end end #{code} RUBY_EVAL when :after class Foo define_callbacks :save, :terminator => 'xxx' set_callback :save, :before, -> { "lambda" } set_callback :save, -> { "lambda1" } end
  15. when :before <<-RUBY_EVAL if !halted && #{@compiled_options} result = result

    = #{@source} halted = (#{chain.config[:terminator]}) if halted halted_callback_hook(#{@raw_filter}) end end #{code} RUBY_EVAL when :after class Foo define_callbacks :save, :terminator => 'xxx' set_callback :save, :before, -> { "lambda" } set_callback :save, -> { "lambda1" } end
  16. when :before <<-RUBY_EVAL if !halted && #{@compiled_options} result = result

    = #{@source} halted = (#{chain.config[:terminator]}) if halted halted_callback_hook(#{@raw_filter}) end end #{code} RUBY_EVAL when :after class Foo define_callbacks :save, :terminator => 'xxx' set_callback :save, :before, -> { "lambda" } set_callback :save, -> { "lambda1" } end
  17. when :before <<-RUBY_EVAL if !halted && #{@compiled_options} result = result

    = #{@source} halted = (#{chain.config[:terminator]}) if halted halted_callback_hook(#{@raw_filter}) end end #{code} RUBY_EVAL when :after class Foo define_callbacks :save, :terminator => 'xxx' set_callback :save, :before, -> { "lambda" } set_callback :save, -> { "lambda1" } end
  18. when :before <<-RUBY_EVAL if !halted && #{@compiled_options} result = result

    = #{@source} halted = (#{chain.config[:terminator]}) if halted halted_callback_hook(#{@raw_filter}) end end #{code} RUBY_EVAL when :after class Foo define_callbacks :save, :terminator => 'xxx' set_callback :save, :before, -> { "lambda" } set_callback :save, -> { "lambda1" } end
  19. when :before <<-RUBY_EVAL if !halted && #{@compiled_options} result = result

    = #{@source} halted = (#{chain.config[:terminator]}) if halted halted_callback_hook(#{@raw_filter}) end end #{code} RUBY_EVAL when :after class Foo define_callbacks :save, :terminator => 'xxx' set_callback :save, :before, -> { "lambda" } set_callback :save, -> { "lambda1" } end
  20. when :before <<-RUBY_EVAL if !halted && #{@compiled_options} result = result

    = #{@source} halted = (#{chain.config[:terminator]}) if halted halted_callback_hook(#{@raw_filter}) end end #{code} RUBY_EVAL when :after class Foo define_callbacks :save, :terminator => 'xxx' set_callback :save, :before, -> { "lambda" } set_callback :save, -> { "lambda1" } end
  21. value = nil halted = false if !halted && true

    result = result = _callback_before_1 halted = (xxx) if halted halted_callback_hook("#<Proc:>") end end if !halted && true result = result = _callback_before_2 halted = (xxx) if halted halted_callback_hook("#<Proc:>") end end value = !halted && (!block_given? || yield) value class Foo define_callbacks :save, :terminator => 'xxx' set_callback :save, :before, -> { "lambda" } set_callback :save, -> { "lambda1" } end User Code Eval’d Code
  22. What we want ❤ Easy to understand ❤ Easy to

    change ❤ Low memory ❤ Fast
  23. def _compile_filter(filter) case filter when Symbol filter when String "(#{filter})"

    when Proc method_name = "_callback_#{@kind}_#{next_id}" @klass.send(:define_method, method_name, &filter) return method_name if filter.arity <= 0 method_name << (filter.arity == 1 ? "(self)" : " self, Proc.new ") else @klass.class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1 def #{method_name}(&blk) #{method_name}_object.send(:#{method_to_call}, self, &blk) end RUBY_EVAL method_name end end Conversion Method
  24. String contains ❤ A method name ❤ Some code to

    eval ❤ A method that changes depending on the arity of the proc
  25. def make_lambda(filter) case filter when Symbol lambda { |target, _,

    &blk| target.send filter, &blk } when String l = eval "lambda { |value| #{filter} }" lambda { |target, value| target.instance_exec(value, &l) } when ::Proc if filter.arity > 1 return lambda { |target, _, &block| raise ArgumentError unless block target.instance_exec(target, block, &filter) } end if filter.arity <= 0 lambda { |target, _| target.instance_exec(&filter) } else lambda { |target, _| target.instance_exec(target, &filter) } end else lambda { |target, _, &blk| filter.public_send method_to_call, target, &blk } end end Conversion Method
  26. Conversion Method when :before <<-RUBY_EVAL if !halted && #{@compiled_options} result

    = result = #{@source} halted = (#{chain.config[:terminator]}) if halted halted_callback_hook(#{@raw_filter}) end end #{code} RUBY_EVAL when :after
  27. if filter_type == :before lambda { halting = halted.call conds

    = conditionals.all? { |c| c.call } if !halting && conds callback_lambda.call end next_callback.call } end “Before” Callback
  28. when :before lambda { |env| target = env.target value =

    env.value halted = env.halted if !halted && user_conditions.all? { |c| c.call(target, value) } result = user_callback.call target, value env.halted = halted_lambda.call result if env.halted target.send :halted_callback_hook, @filter end end next_callback.call env } when :after class Foo define_callbacks :save, :terminator => 'xxx' set_callback :save, :before, -> { "lambda" } set_callback :save, -> { "lambda1" } end
  29. when :before lambda { |env| target = env.target value =

    env.value halted = env.halted if !halted && user_conditions.all? { |c| c.call(target, value) } result = user_callback.call target, value env.halted = halted_lambda.call result if env.halted target.send :halted_callback_hook, @filter end end next_callback.call env } when :after class Foo define_callbacks :save, :terminator => 'xxx' set_callback :save, :before, -> { "lambda" } set_callback :save, -> { "lambda1" } end
  30. when :before lambda { |env| target = env.target value =

    env.value halted = env.halted if !halted && user_conditions.all? { |c| c.call(target, value) } result = user_callback.call target, value env.halted = halted_lambda.call result if env.halted target.send :halted_callback_hook, @filter end end next_callback.call env } when :after class Foo define_callbacks :save, :terminator => 'xxx' set_callback :save, :before, -> { "lambda" } set_callback :save, -> { "lambda1" } end
  31. when :before lambda { |env| target = env.target value =

    env.value halted = env.halted if !halted && user_conditions.all? { |c| c.call(target, value) } result = user_callback.call target, value env.halted = halted_lambda.call result if env.halted target.send :halted_callback_hook, @filter end end next_callback.call env } when :after class Foo define_callbacks :save, :terminator => 'xxx' set_callback :save, :before, -> { "lambda" } set_callback :save, -> { "lambda1" } end
  32. when :before lambda { |env| target = env.target value =

    env.value halted = env.halted if !halted && user_conditions.all? { |c| c.call(target, value) } result = user_callback.call target, value env.halted = halted_lambda.call result if env.halted target.send :halted_callback_hook, @filter end end next_callback.call env } when :after class Foo define_callbacks :save, :terminator => 'xxx' set_callback :save, :before, -> { "lambda" } set_callback :save, -> { "lambda1" } end
  33. when :before lambda { |env| target = env.target value =

    env.value halted = env.halted if !halted && user_conditions.all? { |c| c.call(target, value) } result = user_callback.call target, value env.halted = halted_lambda.call result if env.halted target.send :halted_callback_hook, @filter end end next_callback.call env } when :after class Foo define_callbacks :save, :terminator => 'xxx' set_callback :save, :before, -> { "lambda" } set_callback :save, -> { "lambda1" } end
  34. when :before lambda { |env| target = env.target value =

    env.value halted = env.halted if !halted && user_conditions.all? { |c| c.call(target, value) } result = user_callback.call target, value env.halted = halted_lambda.call result if env.halted target.send :halted_callback_hook, @filter end end next_callback.call env } when :after class Foo define_callbacks :save, :terminator => 'xxx' set_callback :save, :before, -> { "lambda" } set_callback :save, -> { "lambda1" } end
  35. if filter_type == :after lambda { next_callback.call halting = halted.call

    conds = conditionals.all? { |c| c.call } if !halting && conds callback_lambda.call end } end “After” Callback
  36. Generate Lambda when :before # The “before” callback lambda when

    :after # The “after” callback lambda when :around # The “around” callback lambda else # ... end
  37. What we want ❤ Easy to understand ❤ Easy to

    change ❤ Low memory? ❤ Fast?
  38. require 'active_support/callbacks' class Foo include ActiveSupport::Callbacks define_callbacks :save 100_000.times {

    set_callback :save, :before, -> { "lambda" } } end puts "DONE!" GC.start sleep
  39. What we want ❤ Easy to understand ❤ Easy to

    change ❤ Low memory ❤ Fast?
  40. Speed [aaron@higgins rails (4-0-stable)]$ time bundle exec ruby rawr.rb DONE!

    real 0m54.925s user 0m53.928s sys 0m0.498s [aaron@higgins rails (4-0-stable)]$ git checkout 73aefee [aaron@higgins rails (73aefee...)]$ time bundle exec ruby rawr.rb DONE! real 0m17.619s user 0m17.385s sys 0m0.176s [aaron@higgins rails (73aefee...)]$
  41. Speed [aaron@higgins rails (4-0-stable)]$ time bundle exec ruby rawr.rb DONE!

    real 0m54.925s user 0m53.928s sys 0m0.498s [aaron@higgins rails (4-0-stable)]$ git checkout 73aefee [aaron@higgins rails (73aefee...)]$ time bundle exec ruby rawr.rb DONE! real 0m17.619s user 0m17.385s sys 0m0.176s [aaron@higgins rails (73aefee...)]$ 54s 18s
  42. Runtime Speed class User < ActiveRecord::Base validates_presence_of :name end user

    = User.new(name: 'Aaron') user.valid? Benchmark.ips do |x| x.report("valid") { user.valid? } end
  43. Runtime Speed [aaron@higgins rails (4-0-stable)]$ bundle exec ruby test.rb Calculating

    ------------------------------------- valid 3902 i/100ms ------------------------------------------------- valid 51094.4 (±14.0%) i/s - 253630 in 5.059460s [aaron@higgins rails (4-0-stable)]$ [aaron@higgins rails (73aefee...)]$ bundle exec ruby test.rb Calculating ------------------------------------- valid 3726 i/100ms ------------------------------------------------- valid 49397.5 (±12.1%) i/s - 245916 in 5.047747s [aaron@higgins rails (73aefee...)]$ 51k / s 49k / s
  44. lambda { |env| if user_conditions.all? { |c| c.call(target, value) }

    # ... result = user_callback.call target, value # ... end next_callback.call env }
  45. if user_conditions.empty? lambda { |env| # ... result = user_callback.call

    target, value # ... next_callback.call env } else lambda { |env| if user_conditions.all? { |c| c.call(target, value) } # ... result = user_callback.call target, value # ... end next_callback.call env } end
  46. Runtime Speed [aaron@higgins rails (4-0-stable)]$ bundle exec ruby test.rb Calculating

    ------------------------------------- valid 3902 i/100ms ------------------------------------------------- valid 51094.4 (±14.0%) i/s - 253630 in 5.059460s [aaron@higgins rails (4-0-stable)]$ [aaron@higgins rails (master)]$ bundle exec ruby test.rb Calculating ------------------------------------- valid 3953 i/100ms ------------------------------------------------- valid 52240.9 (±10.8%) i/s - 260898 in 5.051079s [aaron@higgins rails (master)]$ 51k / s 52k / s
  47. What we want ❤ Easy to understand ❤ Easy to

    change ❤ Low memory ❤ Fast
  48. AST to SQL SQL AST WHERE ... ... FROM people

    SELECT * SELECT * FROM people WHERE ... AND ...
  49. Post.where(:id => params[:id]).first Generated Query Bind Parameters SELECT * FROM

    people WHERE id = ? [id, 10] SELECT * FROM people WHERE id = ? [id, 15] SELECT * FROM people WHERE id = ? [id, 3]
  50. 1. Generate AR::Relation chain 2. Generate SQL AST 3. Generate

    SQL String 4. Query the database On Every Request
  51. class NoCache def initialize(&blk) @block = blk end def call(*args)

    @block.call(*args).to_a end end nocache = NoCache.new do |name, age| User.where(:name => name).where(:age => age) end No Cache
  52. class Cache def initialize(&blk) @block = blk @relation = nil

    end def call *args @relation ||= @block.call(*args) @relation.set_binds args @relation.to_a end end nocache = Cache.new do |name, age| User.where(:name => name).where(:age => age) end Cache
  53. class Cache def initialize(&blk) @block = blk @relation = nil

    end def call *args @relation ||= @block.call(*args) @relation.set_binds args @relation.to_a end end nocache = Cache.new do |name, age| User.where(:name => name).where(:age => age) end Cache Cache Relation
  54. class Cache def initialize(&blk) @block = blk @relation = nil

    end def call *args @relation ||= @block.call(*args) @relation.set_binds args @relation.to_a end end nocache = Cache.new do |name, age| User.where(:name => name).where(:age => age) end Cache Reset Binds
  55. Benchmark cache = Cache.new do |name, age| User.where(:name => name).where(:age

    => age) end nocache = NoCache.new do |name, age| User.where(:name => name).where(:age => age) end name = names.first Benchmark.ips do |x| x.report("cache") { cache.call(name, 32) } x.report("nocache") { nocache.call(name, 32) } end
  56. Result $ bundle exec ruby qcache.rb Calculating ------------------------------------- cache 456

    i/100ms nocache 278 i/100ms ------------------------------------------------- cache 4754.9 (±8.0%) i/s - 23712 in 5.018513s nocache 2820.4 (±5.9%) i/s - 14178 in 5.046598s
  57. 1. Generate AR::Relation chain 2. Generate SQL AST 3. Generate

    SQL String 4. Query the database On Every Request Cached! Cached!
  58. Fiddle with Ruby require ‘fiddle’ o = Object.new offset =

    o.object_id << 1 pointer = Fiddle::Pointer.new(offset)
  59. RObject Layout struct RObject { struct RBasic basic; union {

    struct { long numiv; VALUE *ivptr; struct st_table *iv_index_tbl; } heap; VALUE ary[ROBJECT_EMBED_LEN_MAX]; } as; };
  60. Read Flags >> require 'fiddle' >> o = Object.new >>

    offset = o.object_id << 1 >> pointer = Fiddle::Pointer.new(offset) >> 8.times.map { |i| pointer[i] } => [33, 0, 0, 0, 0, 0, 0, 0] >>
  61. Get klass >> 8.times.map { |i| pointer[i+8] } => [-64,

    55, -114, -40, -88, 127, 0, 0] >> addr = _.pack('C8').unpack('Q') => [140363164432320] >> Fiddle::Pointer.new(addr.first).to_value => Object >>
  62. Set klass o = Object.new offset = o.object_id << 1

    pointer = Fiddle::Pointer.new(offset) klass_offset = String.object_id << 1 [klass_offset].pack('Q').unpack('C8').each_with_index { |n,i| pointer[i + 8] = n } o.class # => String