Slide 1

Slide 1 text

Hello!

Slide 2

Slide 2 text

WELCOME!

Slide 3

Slide 3 text

THANK YOU!

Slide 4

Slide 4 text

I bought a Google Glass

Slide 5

Slide 5 text

No content

Slide 6

Slide 6 text

No content

Slide 7

Slide 7 text

No content

Slide 8

Slide 8 text

No content

Slide 9

Slide 9 text

No content

Slide 10

Slide 10 text

I love food!

Slide 11

Slide 11 text

Earth (top)

Slide 12

Slide 12 text

Earth (top)

Slide 13

Slide 13 text

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

Slide 14

Slide 14 text

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

Slide 15

Slide 15 text

No content

Slide 16

Slide 16 text

Aaron Patterson @tenderlove

Slide 17

Slide 17 text

Ruby Core Team Rails Core Team

Slide 18

Slide 18 text

Polymorphism Refactoring Caching Irresponsible Ruby

Slide 19

Slide 19 text

WARNING!

Slide 20

Slide 20 text

This talk is extremely technical

Slide 21

Slide 21 text

Polymorphism

Slide 22

Slide 22 text

Hide Complexity

Slide 23

Slide 23 text

Decouple

Slide 24

Slide 24 text

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)

Slide 25

Slide 25 text

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

Slide 26

Slide 26 text

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

Slide 27

Slide 27 text

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

Slide 28

Slide 28 text

Drive method def drive car car.go end

Slide 29

Slide 29 text

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

Slide 30

Slide 30 text

Depends on Type def drive car car.go end

Slide 31

Slide 31 text

Depends on Type def drive car car.go end

Slide 32

Slide 32 text

Logic is removed from the caller

Slide 33

Slide 33 text

“Caches” decision logic

Slide 34

Slide 34 text

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

Slide 35

Slide 35 text

Refactoring

Slide 36

Slide 36 text

Code is a graph

Slide 37

Slide 37 text

if foo && bar do_something else do_something_else end Parse Tree

Slide 38

Slide 38 text

Parse Tree if statement conditions positive negative

Slide 39

Slide 39 text

Parse Tree: Structure, but no meaning

Slide 40

Slide 40 text

if foo && bar do_something else do_something_else end Dependency Tree

Slide 41

Slide 41 text

Dependency Tree if statement conditions positive negative foo bar

Slide 42

Slide 42 text

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

Slide 43

Slide 43 text

Active Support Callbacks API

Slide 44

Slide 44 text

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

Slide 45

Slide 45 text

Results $ ruby test.rb record record1 $

Slide 46

Slide 46 text

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

Slide 47

Slide 47 text

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”

Slide 48

Slide 48 text

6 tests!

Slide 49

Slide 49 text

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”

Slide 50

Slide 50 text

6 * 6 tests!

Slide 51

Slide 51 text

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”

Slide 52

Slide 52 text

6 * 6 * 2 tests!

Slide 53

Slide 53 text

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

Slide 54

Slide 54 text

6 * 6 * 2 * 3 tests!

Slide 55

Slide 55 text

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

Slide 56

Slide 56 text

6 * 6 * 2 * 3 * tests

Slide 57

Slide 57 text

Reality: 21 tests

Slide 58

Slide 58 text

Callback Dependencies

Slide 59

Slide 59 text

set_callback first

Slide 60

Slide 60 text

set_callback first second third etc

Slide 61

Slide 61 text

set_callback first second third etc

Slide 62

Slide 62 text

set_callback first second third etc eval

Slide 63

Slide 63 text

run_callback first second third etc

Slide 64

Slide 64 text

run_callback first second third etc eval

Slide 65

Slide 65 text

run_callbacks callbacks method callback method callback method conds callback conds callback

Slide 66

Slide 66 text

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

Slide 67

Slide 67 text

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

Slide 68

Slide 68 text

Count Methods f = Foo.new x = Foo.instance_methods.length f.run_callbacks :save y = Foo.instance_methods.length - x p NEW_METHODS: y

Slide 69

Slide 69 text

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

Slide 70

Slide 70 text

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

Slide 71

Slide 71 text

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

Slide 72

Slide 72 text

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

Slide 73

Slide 73 text

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

Slide 74

Slide 74 text

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

Slide 75

Slide 75 text

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

Slide 76

Slide 76 text

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

Slide 77

Slide 77 text

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

Slide 78

Slide 78 text

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

Slide 79

Slide 79 text

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

Slide 80

Slide 80 text

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

Slide 81

Slide 81 text

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

Slide 82

Slide 82 text

Leaf Nodes

Slide 83

Slide 83 text

run_callbacks callbacks method callback method callback method conds callback conds callback

Slide 84

Slide 84 text

run_callbacks callbacks method callback method callback method conds callback conds callback

Slide 85

Slide 85 text

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

Slide 86

Slide 86 text

compile_filter

Slide 87

Slide 87 text

compile_filter Object

Slide 88

Slide 88 text

compile_filter String

Slide 89

Slide 89 text

compile_filter

Slide 90

Slide 90 text

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

Slide 91

Slide 91 text

We need CONSISTENCY

Slide 92

Slide 92 text

Return a lambda!

Slide 93

Slide 93 text

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

Slide 94

Slide 94 text

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

Slide 95

Slide 95 text

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

Slide 96

Slide 96 text

Etc...

Slide 97

Slide 97 text

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

Slide 98

Slide 98 text

Move Up

Slide 99

Slide 99 text

run_callbacks callbacks method callback method callback method conds callback conds callback

Slide 100

Slide 100 text

run_callbacks callbacks method callback method callback method conds callback conds callback

Slide 101

Slide 101 text

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

Slide 102

Slide 102 text

compile_method

Slide 103

Slide 103 text

compile_method conditions, callback, next callback

Slide 104

Slide 104 text

compile_method

Slide 105

Slide 105 text

Return a lambda!

Slide 106

Slide 106 text

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

Slide 107

Slide 107 text

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

Slide 108

Slide 108 text

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

Slide 109

Slide 109 text

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

Slide 110

Slide 110 text

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

Slide 111

Slide 111 text

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

Slide 112

Slide 112 text

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

Slide 113

Slide 113 text

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

Slide 114

Slide 114 text

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

Slide 115

Slide 115 text

lambda Linked List

Slide 116

Slide 116 text

lambda lambda lambda Linked List

Slide 117

Slide 117 text

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

Slide 118

Slide 118 text

Switch lambdas based on type. (polymorphism)

Slide 119

Slide 119 text

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

Slide 120

Slide 120 text

Benchmark!

Slide 121

Slide 121 text

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

Slide 122

Slide 122 text

Memory

Slide 123

Slide 123 text

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

Slide 124

Slide 124 text

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

Slide 125

Slide 125 text

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

Slide 126

Slide 126 text

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

Slide 127

Slide 127 text

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

Slide 128

Slide 128 text

Slower. (T_T)

Slide 129

Slide 129 text

More Polymorphism!

Slide 130

Slide 130 text

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

Slide 131

Slide 131 text

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

Slide 132

Slide 132 text

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

Slide 133

Slide 133 text

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

Slide 134

Slide 134 text

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

Slide 135

Slide 135 text

Caching

Slide 136

Slide 136 text

Active Record

Slide 137

Slide 137 text

Architecture

Slide 138

Slide 138 text

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

Slide 139

Slide 139 text

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

Slide 140

Slide 140 text

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

Slide 141

Slide 141 text

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

Slide 142

Slide 142 text

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

Slide 143

Slide 143 text

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

Slide 144

Slide 144 text

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

Slide 145

Slide 145 text

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

Slide 146

Slide 146 text

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

Slide 147

Slide 147 text

Bind Params R ails >= 3.2

Slide 148

Slide 148 text

SELECT * FROM people WHERE id = ?

Slide 149

Slide 149 text

Database Query Database Rails Application

Slide 150

Slide 150 text

Database Query Database Rails Application SQL Binds

Slide 151

Slide 151 text

Database Query Database Rails Application Records

Slide 152

Slide 152 text

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

Slide 153

Slide 153 text

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

Slide 154

Slide 154 text

Query Plan is cached

Slide 155

Slide 155 text

Parsed SQL is cached

Slide 156

Slide 156 text

Better Caching

Slide 157

Slide 157 text

Student Work

Slide 158

Slide 158 text

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

Slide 159

Slide 159 text

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]

Slide 160

Slide 160 text

Only bind parameters change

Slide 161

Slide 161 text

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

Slide 162

Slide 162 text

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

Slide 163

Slide 163 text

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

Slide 164

Slide 164 text

Cache Invariants

Slide 165

Slide 165 text

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

Slide 166

Slide 166 text

Benchmarks

Slide 167

Slide 167 text

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

Slide 168

Slide 168 text

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

Slide 169

Slide 169 text

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

Slide 170

Slide 170 text

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

Slide 171

Slide 171 text

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

Slide 172

Slide 172 text

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

Slide 173

Slide 173 text

0 1250 2500 3750 5000 Queries per second Cached Not Cached

Slide 174

Slide 174 text

1.68 times faster!

Slide 175

Slide 175 text

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

Slide 176

Slide 176 text

Invalidation

Slide 177

Slide 177 text

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

Slide 178

Slide 178 text

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

Slide 179

Slide 179 text

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

Slide 180

Slide 180 text

We can go even faster!

Slide 181

Slide 181 text

Refactoring is the gateway to speed

Slide 182

Slide 182 text

Use polymorphism

Slide 183

Slide 183 text

Cache Invariants

Slide 184

Slide 184 text

Irresponsible Ruby

Slide 185

Slide 185 text

Ruby Values ❤ Immediate ❤ Regular

Slide 186

Slide 186 text

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

Slide 187

Slide 187 text

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

Slide 188

Slide 188 text

Regular Values

Slide 189

Slide 189 text

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

Slide 190

Slide 190 text

fiddle with memory

Slide 191

Slide 191 text

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

Slide 192

Slide 192 text

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

Slide 193

Slide 193 text

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

Slide 194

Slide 194 text

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

Slide 195

Slide 195 text

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

Slide 196

Slide 196 text

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

Slide 197

Slide 197 text

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

Slide 198

Slide 198 text

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

Slide 199

Slide 199 text

Lets set the class!

Slide 200

Slide 200 text

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

Slide 201

Slide 201 text

No content

Slide 202

Slide 202 text

Have Fun at RedDotRubyConf!

Slide 203

Slide 203 text

No content