RedDotRubyConf

 RedDotRubyConf

F29327647a9cff5c69618bae420792ea?s=128

Aaron Patterson

June 07, 2013
Tweet

Transcript

  1. 1.
  2. 5.
  3. 6.
  4. 7.
  5. 8.
  6. 9.
  7. 13.

    Radius at Latitude R = radius of earth a =

    equatorial radius b = polar radius l = geodetic latitude
  8. 15.
  9. 19.
  10. 23.
  11. 24.

    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)
  12. 25.

    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
  13. 27.

    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
  14. 29.

    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
  15. 44.

    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
  16. 47.

    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”
  17. 48.
  18. 49.

    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”
  19. 51.

    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”
  20. 53.

    Variations, location class Foo set_callback :save, :before, -> { "lambda"

    } set_callback :save, :after, -> { "lambda" } set_callback :save, :around, -> { "lambda" } end
  21. 66.

    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
  22. 68.

    Count Methods f = Foo.new x = Foo.instance_methods.length f.run_callbacks :save

    y = Foo.instance_methods.length - x p NEW_METHODS: y
  23. 70.

    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
  24. 71.

    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
  25. 72.

    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
  26. 73.

    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
  27. 74.

    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
  28. 75.

    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
  29. 76.

    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
  30. 77.

    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
  31. 78.

    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
  32. 80.

    What we want ❤ Easy to understand ❤ Easy to

    change ❤ Low memory ❤ Fast
  33. 85.

    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
  34. 90.

    String contains ❤ A method name ❤ Some code to

    eval ❤ A method that changes depending on the arity of the proc
  35. 96.
  36. 97.

    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
  37. 98.
  38. 101.

    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
  39. 106.

    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
  40. 107.

    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
  41. 108.

    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
  42. 109.

    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
  43. 110.

    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
  44. 111.

    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
  45. 112.

    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
  46. 113.

    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
  47. 114.

    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
  48. 117.

    Generate Lambda when :before # The “before” callback lambda when

    :after # The “after” callback lambda when :around # The “around” callback lambda else # ... end
  49. 119.

    What we want ❤ Easy to understand ❤ Easy to

    change ❤ Low memory? ❤ Fast?
  50. 120.
  51. 121.

    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
  52. 122.
  53. 123.

    What we want ❤ Easy to understand ❤ Easy to

    change ❤ Low memory ❤ Fast?
  54. 124.

    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...)]$
  55. 125.

    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
  56. 126.

    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
  57. 127.

    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
  58. 130.

    lambda { |env| if user_conditions.all? { |c| c.call(target, value) }

    # ... result = user_callback.call target, value # ... end next_callback.call env }
  59. 132.

    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
  60. 133.

    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
  61. 134.

    What we want ❤ Easy to understand ❤ Easy to

    change ❤ Low memory ❤ Fast
  62. 135.
  63. 145.

    AST to SQL SQL AST WHERE ... ... FROM people

    SELECT * SELECT * FROM people WHERE ... AND ...
  64. 159.

    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]
  65. 163.

    1. Generate AR::Relation chain 2. Generate SQL AST 3. Generate

    SQL String 4. Query the database On Every Request
  66. 166.
  67. 167.

    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
  68. 168.

    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
  69. 169.

    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
  70. 170.

    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
  71. 171.

    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
  72. 172.

    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
  73. 175.

    1. Generate AR::Relation chain 2. Generate SQL AST 3. Generate

    SQL String 4. Query the database On Every Request Cached! Cached!
  74. 191.
  75. 192.

    Fiddle with Ruby require ‘fiddle’ o = Object.new offset =

    o.object_id << 1 pointer = Fiddle::Pointer.new(offset)
  76. 193.

    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; };
  77. 195.
  78. 196.

    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] >>
  79. 197.
  80. 198.

    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 >>
  81. 200.

    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
  82. 201.
  83. 203.