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

RedDotRubyConf

 RedDotRubyConf

Aaron Patterson

June 07, 2013
Tweet

More Decks by Aaron Patterson

Other Decks in Technology

Transcript

  1. Hello!

    View Slide

  2. WELCOME!

    View Slide

  3. THANK YOU!

    View Slide

  4. I bought a
    Google Glass

    View Slide

  5. View Slide

  6. View Slide

  7. View Slide

  8. View Slide

  9. View Slide

  10. I love food!

    View Slide

  11. Earth (top)

    View Slide

  12. Earth (top)

    View Slide

  13. Radius at Latitude
    R = radius of earth
    a = equatorial radius
    b = polar radius
    l = geodetic latitude

    View Slide

  14. Seattle Singapore
    Latitude 47.6097° N 1.3667° N
    Radius 6,373 km 6,357 km

    View Slide

  15. View Slide

  16. Aaron Patterson
    @tenderlove

    View Slide

  17. Ruby Core Team
    Rails Core Team

    View Slide

  18. Polymorphism
    Refactoring
    Caching
    Irresponsible Ruby

    View Slide

  19. WARNING!

    View Slide

  20. This talk is
    extremely technical

    View Slide

  21. Polymorphism

    View Slide

  22. Hide Complexity

    View Slide

  23. Decouple

    View Slide

  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)

    View Slide

  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

    View Slide

  26. Both Cars
    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

    View Slide

  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

    View Slide

  28. Drive method
    def drive car
    car.go
    end

    View Slide

  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

    View Slide

  30. Depends on Type
    def drive car
    car.go
    end

    View Slide

  31. Depends on Type
    def drive car
    car.go
    end

    View Slide

  32. Logic is removed
    from the caller

    View Slide

  33. “Caches”
    decision logic

    View Slide

  34. def drive car
    car.go
    end
    car = Automatic.new
    drive car
    “Cache”

    View Slide

  35. Refactoring

    View Slide

  36. Code is a graph

    View Slide

  37. if foo && bar
    do_something
    else
    do_something_else
    end
    Parse Tree

    View Slide

  38. Parse Tree
    if statement
    conditions positive negative

    View Slide

  39. Parse Tree:
    Structure, but no meaning

    View Slide

  40. if foo && bar
    do_something
    else
    do_something_else
    end
    Dependency Tree

    View Slide

  41. Dependency Tree
    if statement
    conditions positive negative
    foo bar

    View Slide

  42. Dependency Tree
    if statement
    conditions positive negative
    foo bar ????

    View Slide

  43. Active Support
    Callbacks API

    View Slide

  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

    View Slide

  45. Results
    $ ruby test.rb
    record
    record1
    $

    View Slide

  46. Active Record
    class Person < ActiveRecord::Base
    validates_presence_of :name
    validates_length_of :name, maximum:30
    end

    View Slide

  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”

    View Slide

  48. 6 tests!

    View Slide

  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”

    View Slide

  50. 6 * 6 tests!

    View Slide

  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”

    View Slide

  52. 6 * 6 * 2 tests!

    View Slide

  53. Variations, location
    class Foo
    set_callback :save, :before, -> { "lambda" }
    set_callback :save, :after, -> { "lambda" }
    set_callback :save, :around, -> { "lambda" }
    end

    View Slide

  54. 6 * 6 * 2 * 3 tests!

    View Slide

  55. Variations, halt
    class Foo
    include ActiveSupport::Callbacks
    define_callbacks :save, terminator: "result == false"
    end

    View Slide

  56. 6 * 6 * 2 * 3 *
    tests

    View Slide

  57. Reality: 21 tests

    View Slide

  58. Callback
    Dependencies

    View Slide

  59. set_callback
    first

    View Slide

  60. set_callback
    first
    second
    third
    etc

    View Slide

  61. set_callback
    first
    second
    third
    etc

    View Slide

  62. set_callback
    first
    second
    third
    etc eval

    View Slide

  63. run_callback
    first
    second
    third
    etc

    View Slide

  64. run_callback
    first
    second
    third
    etc eval

    View Slide

  65. run_callbacks
    callbacks method
    callback method callback method
    conds callback conds callback

    View Slide

  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

    View Slide

  67. Run It
    $ ruby test.rb
    {:NEW_METHODS=>100}
    $

    View Slide

  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

    View Slide

  69. Run It
    $ ruby test.rb
    {:NEW_METHODS=>1}
    $

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  78. value = nil
    halted = false
    if !halted && true
    result = result = _callback_before_1
    halted = (xxx)
    if halted
    halted_callback_hook("#")
    end
    end
    if !halted && true
    result = result = _callback_before_2
    halted = (xxx)
    if halted
    halted_callback_hook("#")
    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

    View Slide

  79. Currently
    ❤ Hard to understand
    ❤ Hard to change
    ❤ Memory Intensive
    ❤ Fast

    View Slide

  80. What we want
    ❤ Easy to understand
    ❤ Easy to change
    ❤ Low memory
    ❤ Fast

    View Slide

  81. ❤ Conditionals (:if, :unless)
    ❤ Callback body code
    ❤ Termination code
    Dependencies

    View Slide

  82. Leaf Nodes

    View Slide

  83. run_callbacks
    callbacks method
    callback method callback method
    conds callback conds callback

    View Slide

  84. run_callbacks
    callbacks method
    callback method callback method
    conds callback conds callback

    View Slide

  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

    View Slide

  86. compile_filter

    View Slide

  87. compile_filter
    Object

    View Slide

  88. compile_filter
    String

    View Slide

  89. compile_filter

    View Slide

  90. String contains
    ❤ A method name
    ❤ Some code to eval
    ❤ A method that changes depending on
    the arity of the proc

    View Slide

  91. We need
    CONSISTENCY

    View Slide

  92. Return a lambda!

    View Slide

  93. if object.is_a?(Symbol)
    lambda { |target,object|
    object.send target
    }
    end
    Handle Symbols

    View Slide

  94. Handle Lambdas
    if object.is_a?(Proc)
    object
    end

    View Slide

  95. Handle Objects
    if object.is_a?(Object)
    lambda { |target, object|
    object.before_filter(target)
    }
    end

    View Slide

  96. Etc...

    View Slide

  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

    View Slide

  98. Move Up

    View Slide

  99. run_callbacks
    callbacks method
    callback method callback method
    conds callback conds callback

    View Slide

  100. run_callbacks
    callbacks method
    callback method callback method
    conds callback conds callback

    View Slide

  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

    View Slide

  102. compile_method

    View Slide

  103. compile_method
    conditions,
    callback,
    next callback

    View Slide

  104. compile_method

    View Slide

  105. Return a lambda!

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  115. lambda
    Linked List

    View Slide

  116. lambda lambda lambda
    Linked List

    View Slide

  117. Generate Lambda
    when :before
    # The “before” callback lambda
    when :after
    # The “after” callback lambda
    when :around
    # The “around” callback lambda
    else
    # ...
    end

    View Slide

  118. Switch lambdas
    based on type.
    (polymorphism)

    View Slide

  119. What we want
    ❤ Easy to understand
    ❤ Easy to change
    ❤ Low memory?
    ❤ Fast?

    View Slide

  120. Benchmark!

    View Slide

  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

    View Slide

  122. Memory

    View Slide

  123. What we want
    ❤ Easy to understand
    ❤ Easy to change
    ❤ Low memory
    ❤ Fast?

    View Slide

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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  128. Slower. (T_T)

    View Slide

  129. More
    Polymorphism!

    View Slide

  130. lambda { |env|
    if user_conditions.all? { |c| c.call(target, value) }
    # ...
    result = user_callback.call target, value
    # ...
    end
    next_callback.call env
    }

    View Slide

  131. irb(main):001:0> [].all?
    => true
    irb(main):002:0>
    No conditions

    View Slide

  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

    View Slide

  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

    View Slide

  134. What we want
    ❤ Easy to understand
    ❤ Easy to change
    ❤ Low memory
    ❤ Fast

    View Slide

  135. Caching

    View Slide

  136. Active Record

    View Slide

  137. Architecture

    View Slide

  138. Person.where(...).where(...)

    View Slide

  139. AR::Relation
    AR::Relation
    AR::Base
    Person.where(...).where(...)
    Person
    .where(...)
    .where(...)

    View Slide

  140. AR::Relation
    AR::Relation
    AR::Base
    Person.where(...).where(...).to_a

    View Slide

  141. AR::Relation
    AR::Relation
    AR::Base
    Person.where(...).where(...).to_a
    Arel::Manager
    SQL AST
    WHERE
    ...

    View Slide

  142. AR::Relation
    AR::Relation
    AR::Base
    Person.where(...).where(...).to_a
    Arel::Manager
    SQL AST
    WHERE
    ...
    ...

    View Slide

  143. AR::Relation
    AR::Relation
    AR::Base
    Person.where(...).where(...).to_a
    Arel::Manager
    SQL AST
    WHERE
    ...
    ...
    FROM
    people
    SELECT
    *

    View Slide

  144. AST to SQL
    SELECT * FROM people
    WHERE ... AND ...

    View Slide

  145. AST to SQL
    SQL AST
    WHERE
    ...
    ...
    FROM
    people
    SELECT
    *
    SELECT * FROM people
    WHERE ... AND ...

    View Slide

  146. 3 Transformations
    1. Code to AR::Relation
    2. AR::Relation to SQL AST
    3. SQL AST to SQL statement

    View Slide

  147. Bind Params
    R
    ails
    >=
    3.2

    View Slide

  148. SELECT *
    FROM people
    WHERE id = ?

    View Slide

  149. Database Query
    Database
    Rails Application

    View Slide

  150. Database Query
    Database
    Rails Application
    SQL Binds

    View Slide

  151. Database Query
    Database
    Rails Application
    Records

    View Slide

  152. Database Work
    ❤ Parse SQL
    ❤ Plan Query
    ❤ Execute Query
    ❤ Return Results

    View Slide

  153. Database Work
    ❤ Parse SQL
    ❤ Plan Query
    ❤ Execute Query
    ❤ Return Results

    View Slide

  154. Query Plan
    is cached

    View Slide

  155. Parsed SQL
    is cached

    View Slide

  156. Better Caching

    View Slide

  157. Student Work

    View Slide

  158. Find Records
    Person.where(:id => 10).first
    Person.where(:id => 15).first
    Person.where(:id => 3).first

    View Slide

  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]

    View Slide

  160. Only bind
    parameters change

    View Slide

  161. class MyController
    def index
    person = Person.where(:id => params[:id]).first
    end
    end

    View Slide

  162. class MyController
    def index
    person = Person.where(:id => params[:id]).first
    end
    end
    Only dynamic value

    View Slide

  163. 1. Generate AR::Relation chain
    2. Generate SQL AST
    3. Generate SQL String
    4. Query the database
    On Every Request

    View Slide

  164. Cache Invariants

    View Slide

  165. relation = Post.where(:id => 10)
    loop do
    relation.set_binds([rand(100)])
    relation.to_a
    end
    R
    ails
    >=
    4.1

    View Slide

  166. Benchmarks

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  173. 0
    1250
    2500
    3750
    5000
    Queries per second
    Cached Not Cached

    View Slide

  174. 1.68 times faster!

    View Slide

  175. 1. Generate AR::Relation chain
    2. Generate SQL AST
    3. Generate SQL String
    4. Query the database
    On Every Request
    Cached!
    Cached!

    View Slide

  176. Invalidation

    View Slide

  177. Record Cache
    x = Post.where(:name => ‘xxx’)
    x.to_a
    x.to_a
    x.to_a

    View Slide

  178. Invalidation
    def set_binds(list)
    @loaded = nil
    @records = []
    list.zip(values[:bind]).each do |val, bv|
    bv[1] = val
    end
    end

    View Slide

  179. Record Cache
    x = Post.where(:name => ‘xxx’)
    x.to_a
    x.set_binds(...) # cache is invalidated
    x.to_a
    x.to_a

    View Slide

  180. We can go
    even faster!

    View Slide

  181. Refactoring is the
    gateway to speed

    View Slide

  182. Use polymorphism

    View Slide

  183. Cache Invariants

    View Slide

  184. Irresponsible
    Ruby

    View Slide

  185. Ruby Values
    ❤ Immediate
    ❤ Regular

    View Slide

  186. Immediate Values
    ❤ nil
    ❤ true
    ❤ false
    ❤ Fixnum (2 ** (0.size * 8 - 2) - 1)

    View Slide

  187. 10.object_id >> 1 # => 10
    14.object_id >> 1 # => 14
    Fixnum Trick

    View Slide

  188. Regular Values

    View Slide

  189. >> o = Object.new
    => #
    >> o.object_id.to_s(16)
    => "3fd5ad9c5de8"
    >> (o.object_id << 1).to_s(16)
    => "7fab5b38bbd0"
    >>

    View Slide

  190. fiddle with memory

    View Slide

  191. Get a pointer
    require ‘fiddle’
    pointer = Fiddle::Pointer.new(offset)
    pointer[0] # => get the char * val
    pointer[0] = x # => set the val

    View Slide

  192. Fiddle with Ruby
    require ‘fiddle’
    o = Object.new
    offset = o.object_id << 1
    pointer = Fiddle::Pointer.new(offset)

    View Slide

  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;
    };

    View Slide

  194. RBasic
    struct RBasic {
    VALUE flags;
    const VALUE klass;
    }

    View Slide

  195. What is VALUE?
    typedef uintptr_t VALUE;
    >> require 'fiddle'
    => false
    >> Fiddle::SIZEOF_UINTPTR_T
    => 8

    View Slide

  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]
    >>

    View Slide

  197. Read klass
    >> 8.times.map { |i| pointer[i+8] }
    => [-64, 55, -114, -40, -88, 127, 0, 0]
    >>

    View Slide

  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
    >>

    View Slide

  199. Lets set the class!

    View Slide

  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

    View Slide

  201. View Slide

  202. Have Fun at
    RedDotRubyConf!

    View Slide

  203. View Slide