Upgrade to PRO for Only $50/Year—Limited-Time Offer! 🔥

RedDotRubyConf

 RedDotRubyConf

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