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

Speed up Rails, Speed up your code

F29327647a9cff5c69618bae420792ea?s=47 Aaron Patterson
July 02, 2014
70

Speed up Rails, Speed up your code

F29327647a9cff5c69618bae420792ea?s=128

Aaron Patterson

July 02, 2014
Tweet

Transcript

  1. Hello!!!

  2. Welcome!

  3. None
  4. Thanks!!!

  5. YOU PARTY ME!

  6. As Loooonnnngg As Possible

  7. None
  8. Aaron Patterson @tenderlove

  9. None
  10. None
  11. Ruby Core Rails Core

  12. This is my first time to give a talk.

  13. Twitter: tenderlove GitHub: tenderlove Instagram: tenderlove Yo: tenderlove

  14. ಠ_ಠ ಠ_ಠ Separation Of Concerns

  15. OMG! INTERNET! POINTS

  16. Revert Commits Count Too!

  17. More mistakes == more points!!!!

  18. Short Stack Engineer

  19. None
  20. None
  21. Dad-Joke Programmer

  22. Gorbachev Puff Puff Thunderhorse

  23. SEA-TAC Airport YouTube

  24. None
  25. None
  26. Last Year

  27. Hawker Centers

  28. None
  29. None
  30. I love food!

  31. Earth (top)

  32. Radius at Latitude R = radius of earth! a =

    equatorial radius! b = polar radius! l = geodetic latitude
  33. Seattle Singapore Latitude 47.6097° N 1.3667° N Radius 6,373 km

    6,357 km
  34. None
  35. None
  36. WE MISS YOU,! JIM!

  37. Durian Fruit!!!

  38. None
  39. None
  40. None
  41. None
  42. Node.JS

  43. Closer to the METAL

  44. None
  45. Speed up Rails,! Speed up your Code

  46. Rails.inspect

  47. THIS IS NOT MAGENTA

  48. Performance! Tradeoffs

  49. Speed vs! Memory

  50. Time vs! Space

  51. VS

  52. Space! isn’t free.

  53. Time! isn’t free.

  54. Nothing! is free.

  55. VS

  56. Find a better algorithm.

  57. Mystical Unicorn

  58. Making Tradeoffs

  59. RAM is cheap. (for web developers) SORRY HEROKU!

  60. SPEED at the! cost of RAM.

  61. Time and space are related.

  62. Performance Tools

  63. Raw Performance

  64. benchmark/ips

  65. benchmark Benchmark.bm do |x| x.report('some test') { N.times { some_test

    } } end How big? STD LIB
  66. Output user system total real some test 0.000000 0.000000 0.000000

    ( 0.000098)
  67. benchmark/ips require 'benchmark/ips' require 'set' ! list = ('a'..'zzzz').to_a set

    = Set.new list ! Benchmark.ips do |x| x.report("set access") { set.include? "foo" } ! x.report("ary access") { list.include? "foo" } end G EM
  68. Output Calculating ------------------------------------- set access 68622 i/100ms ary access 395

    i/100ms ------------------------------------------------- set access 3047175.3 (±12.7%) i/s - 14959596 in 5.018692s ary access 3899.2 (±7.1%) i/s - 19750 in 5.096118s IPS
  69. Set Include: 3047175.3 / sec

  70. Array Include: 3899.2 / sec

  71. IPS: Higher Is Better

  72. Output Calculating ------------------------------------- set access 68622 i/100ms ary access 395

    i/100ms ------------------------------------------------- set access 3047175.3 (±12.7%) i/s - 14959596 in 5.018692s ary access 3899.2 (±7.1%) i/s - 19750 in 5.096118s STDDEV
  73. benchmark N = 100000 ! list = ('a'..'zzz').to_a hash =

    list.each_with_object({}) { |x,h| h[x] = true } set = Set.new list ! Benchmark.bm do |x| x.report("set access") { N.times { set.include? "foo" } } ! x.report("hash access") { N.times { hash.include? "foo" } } end STD LIB
  74. Output user system total real set access 0.030000 0.000000 0.030000

    ( 0.030044) hash access 0.030000 0.000000 0.030000 ( 0.032125) Set Is Faster? benchm ark
  75. benchmark/ips list = ('a'..'zzz').to_a hash = list.each_with_object({}) { |x,h| h[x]

    = true } set = Set.new list ! Benchmark.ips do |x| x.report("set access") { set.include? "foo" } x.report("hash access") { hash.include? "foo" } end G EM
  76. Output Calculating ------------------------------------- set access 73910 i/100ms hash access 73845

    i/100ms ------------------------------------------------- set access 3081455.6 (±7.6%) i/s - 15299370 in 4.999343s hash access 3772358.3 (±7.2%) i/s - 18756630 in 5.004747s benchm ark/ips
  77. IPS Graph 0 1000000 2000000 3000000 4000000 Iterations / Sec

    Set access Hash Access
  78. Blackbox Testing

  79. Cache Impls cache1 = Cache1.new cache2 = Cache2.new ! cache1["x"]

    = Object.new cache2["x"] = Object.new ! Benchmark.ips do |x| x.report("cache1") { cache1["x"] } x.report("cache2") { cache2["x"] } end
  80. Collect Reports reports = [10, 100, 1000, 100_000].map do |i|

    cache1 = Cache1.new cache2 = Cache2.new ! (i - 1).times { |z| cache2[z.to_s] = cache1[z.to_s] = Object.new } ! cache1["x"] = Object.new cache2["x"] = Object.new ! report = Benchmark.ips do |x| x.report("cache1") { cache1["x"] } x.report("cache2") { cache2["x"] } end [i, report] end Report
  81. Compile Data header = nil rows = reports.map { |i,report|

    header ||= [nil] + report.map(&:label) ["#{i} elements"] + report.map { |r| (1 / r.ips) * 10_000 } } puts header.join ',' rows.each { |r| puts r.join ',' } Seconds Per Iteration Seconds for 10k iters
  82. Runtime Graph Time for 10,000 iterations (seconds) 0.01 0.1 1

    10 100 Cache Size 10 elements 100 elements 1000 elements 100000 elements Cache 1 Cache 2
  83. Cache Implementation class Cache1 def initialize @cache = {} end

    def [] k; @cache[k]; end def []= k,v; @cache[k] = v; end end ! class Cache2 def initialize @cache = [] end def [] k; x, = @cache.assoc(k); x; end def []= k,v; @cache << [k, v]; end end Constant Linear
  84. Real World Example: Routes

  85. Number Of Routes class MyTest routes = ActionDispatch::Routing::RouteSet.new routes.draw {

    resources(:articles) } end ! article = Article.new.tap(&:save!) ! Benchmark.ips do |x| x.report("link_to") { test.link_to "zomg", article } end
  86. Add 10 routes class MyTest routes = ActionDispatch::Routing::RouteSet.new routes.draw {

    resources(:articles) 10.times do |num| resources num.to_s.to_sym end } end
  87. Add 100 routes class MyTest routes = ActionDispatch::Routing::RouteSet.new routes.draw {

    resources(:articles) 100.times do |num| resources num.to_s.to_sym end } end
  88. Add 1000 routes class MyTest routes = ActionDispatch::Routing::RouteSet.new routes.draw {

    resources(:articles) 1000.times do |num| resources num.to_s.to_sym end } end
  89. Sec / 100k calls 9.5 9.7 9.9 10.1 10.3 1

    2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 link_to
  90. Size of URL class MyTest routes = ActionDispatch::Routing::RouteSet.new link =

    'a' ! routes.draw { get "/#{link}/:id", :as => :article, :controller => :articles, :action => :show } end ! test = MyTest.new article = Article.new.tap(&:save!) ! puts "Model Instance" Benchmark.ips do |x| x.report("link_to") { test.link_to "zomg", article } end
  91. Length of 1 class MyTest routes = ActionDispatch::Routing::RouteSet.new link =

    1.times.map(&:to_s).join '/' ! routes.draw { get "/#{link}/:id", :as => :article, :controller => :articles, :action => :show } end
  92. Length of 10 class MyTest routes = ActionDispatch::Routing::RouteSet.new link =

    10.times.map(&:to_s).join '/' ! routes.draw { get "/#{link}/:id", :as => :article, :controller => :articles, :action => :show } end
  93. Length of 100 class MyTest routes = ActionDispatch::Routing::RouteSet.new link =

    100.times.map(&:to_s).join '/' ! routes.draw { get "/#{link}/:id", :as => :article, :controller => :articles, :action => :show } end
  94. Sec / 100k calls 9 10.5 12 13.5 15 1

    2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 link_to
  95. Where is time spent?

  96. stackprof https://github.com/tmm1/stackprof

  97. Profiling url_for require 'stackprof' StackProf.run(mode: :cpu, out: 'url_for.dump') do 5000.times

    { test.url_for article } end
  98. View Results $ stackprof url_for.dump --text

  99. ================================== Mode: cpu(1000) Samples: 218 (0.00% miss rate) GC: 27

    (12.39%) ================================== TOTAL (pct) SAMPLES (pct) FRAME 149 (68.3%) 57 (26.1%) ActionDispatch::Routing::RouteSet#url_for 183 (83.9%) 19 (8.7%) ActionDispatch::::UrlHelper#call 18 (8.3%) 18 (8.3%) #<Module:0x007fb2f4ea3db0>.build_host_url 14 (6.4%) 13 (6.0%) ActionDispatch::UrlHelper#handle_positional_args 68 (31.2%) 12 (5.5%) ActionDispatch::Journey::Formatter#generate 66 (30.3%) 12 (5.5%) ActionDispatch::Formatter#visit_CAT 10 (4.6%) 10 (4.6%) ActionDispatch::Utils::UriEncoder#escape 56 (25.7%) 7 (3.2%) block in ActionDispatch::Formatter#generate 13 (6.0%) 5 (2.3%) ActionDispatch#extract_parameterized_parts
  100. GC.stat()

  101. Total allocations GC.stat(:total_allocated_object)

  102. Measure Allocations Person.find id before = GC.stat(:total_allocated_object) N.times { Person.find

    id } after = GC.stat(:total_allocated_object) puts (after - before) / N W arm up Benchmark Objects / Call Count O bjs C ount O bjs
  103. Real World Example: Views

  104. Request Benchmark task :allocated_objects do app = Ko1TestApp::Application.instance app.app do_test_task(app)

    env = rackenv "/books/new" do_test_task(app, env.dup) before = GC.stat :total_allocated_object TEST_CNT.times { do_test_task(app, env.dup) } after = GC.stat :total_allocated_object puts (after - before) / TEST_CNT end "/books/new"
  105. Request Benchmark task :allocated_objects do app = Ko1TestApp::Application.instance app.app do_test_task(app)

    env = rackenv "/books/new" do_test_task(app, env.dup) before = GC.stat :total_allocated_object TEST_CNT.times { do_test_task(app, env.dup) } after = GC.stat :total_allocated_object puts (after - before) / TEST_CNT end
  106. Request Benchmark task :allocated_objects do app = Ko1TestApp::Application.instance app.app do_test_task(app)

    env = rackenv "/books/new" do_test_task(app, env.dup) before = GC.stat :total_allocated_object TEST_CNT.times { do_test_task(app, env.dup) } after = GC.stat :total_allocated_object puts (after - before) / TEST_CNT end
  107. Test Results Object Allocations Per Request 2000 2150 2300 2450

    2600 4-0-stable 4-1-stable master 2000
  108. HOW TO LIE WITH GRAPHS

  109. Test Results Object Allocations Per Request 0 650 1300 1950

    2600 4-0-stable 4-1-stable master
  110. ~19% reduction since 4-0-stable

  111. ~14% reduction since 4-1-stable

  112. allocation_tracer https://github.com/ko1/allocation_tracer

  113. Example ObjectSpace::AllocationTracer.trace do 1000.times { ["foo", {}] } end !

    ObjectSpace::AllocationTracer.allocated_count_table
  114. Output {:T_NONE=>0, :T_OBJECT=>0, :T_CLASS=>0, :T_MODULE=>0, :T_FLOAT=>0, :T_STRING=>1000, :T_REGEXP=>0, :T_ARRAY=>1000, :T_HASH=>1000,

    :T_ZOMBIE=>0}
  115. Speeding up ActiveRecord

  116. ~3 Years of work

  117. How! ActiveRecord! works.

  118. ActiveRecord::Relation Post.find(10) ARel::SQL::Node Database ActiveRecord::Base SELECT * FROM …

  119. Part 1

  120. Bind Parameter Introduction

  121. Post.find(1) Post.find(3) Post.find(5) SELECT * FROM posts WHERE id =

    1 SELECT * FROM posts WHERE id = 3 SELECT * FROM posts WHERE id = 5
  122. Post.find(1) Post.find(3) Post.find(5) SELECT * FROM posts WHERE id =

    ? [id, 1] SELECT * FROM posts WHERE id = ? [id, 3] SELECT * FROM posts WHERE id = ? [id, 5]
  123. Separate static and dynamic content

  124. Theory

  125. ActiveRecord::Relation Post.find(10) ARel::SQL::Node Database ActiveRecord::Base SELECT * FROM … Cacheable

  126. Part 2

  127. Code Decoupling

  128. def add_constraints(scope) tables = construct_tables ! chain.each_with_index do |reflection, i|

    table, foreign_table = tables.shift, tables.first ! if reflection.source_macro == :has_and_belongs_to_many join_table = tables.shift ! scope = scope.joins(join( join_table, table[reflection.association_primary_key]. eq(join_table[reflection.association_foreign_key]) )) ! table, foreign_table = join_table, tables.first end ! if reflection.source_macro == :belongs_to if reflection.options[:polymorphic] key = reflection.association_primary_key(klass) else key = reflection.association_primary_key end ! foreign_key = reflection.foreign_key else key = reflection.foreign_key foreign_key = reflection.active_record_primary_key end ! if reflection == chain.last bind_val = bind scope, table.table_name, key.to_s, owner[foreign_key] scope = scope.where(table[key].eq(bind_val)) ! if reflection.type value = owner.class.base_class.name bind_val = bind scope, table.table_name, reflection.type.to_s, value scope = scope.where(table[reflection.type].eq(bind_val)) end else constraint = table[key].eq(foreign_table[foreign_key]) ! if reflection.type type = chain[i + 1].klass.base_class.name constraint = constraint.and(table[reflection.type].eq(type)) end ! scope = scope.joins(join(foreign_table, constraint)) end ! # Exclude the scope of the association itself, because that # was already merged in the #scope method. (scope_chain[i] - [self.reflection.scope]).each do |scope_chain_item| item = eval_scope(reflection.klass, scope_chain_item) ! scope.includes! item.includes_values scope.where_values += item.where_values end end ! scope end
  129. One Method to Rule Them All, And In Legacy Code

    Bind Them.
  130. rm habtm R ails 4.1

  131. habtm.is_a?(hm:t) #=> true

  132. commit 88c009377851912c60fd16ec4bfab3001ac2cf9f Author: Aaron Patterson <aaron.patterson@gmail.com> Date: Wed Oct 2

    15:53:56 2013 -0700 ! remove HABTM special cases from associations classes ! diff --git a/activerecord/lib/active_record/associations/association_scope.rb b/activerecord/lib/ active_record/associations/association_scope.rb index 8027acf..d862a5f 100644 --- a/activerecord/lib/active_record/associations/association_scope.rb +++ b/activerecord/lib/active_record/associations/association_scope.rb @@ -44,18 +44,6 @@ module ActiveRecord chain.each_with_index do |reflection, i| table, foreign_table = tables.shift, tables.first - if reflection.source_macro == :has_and_belongs_to_many - join_table = tables.shift - - scope = scope.joins(join( - join_table, - table[reflection.association_primary_key]. - eq(join_table[reflection.association_foreign_key]) - )) - - table, foreign_table = join_table, tables.first - end - if reflection.source_macro == :belongs_to if reflection.options[:polymorphic] key = reflection.association_primary_key(self.klass) diff --git a/activerecord/lib/active_record/associations/join_dependency.rb b/activerecord/lib/ active_record/associations/join_dependency.rb index 5aa17e5..fa212f3 100644 --- a/activerecord/lib/active_record/associations/join_dependency.rb +++ b/activerecord/lib/active_record/associations/join_dependency.rb @@ -216,7 +216,7 @@ module ActiveRecord else association = join_part.instantiate(row) unless row[join_part.aliased_primary_key].nil? case macro - when :has_many, :has_and_belongs_to_many + when :has_many other = record.association(join_part.reflection.name) other.loaded! other.target.push(association) if association diff --git a/activerecord/lib/active_record/associations/join_dependency/join_association.rb b/ activerecord/lib/active_record/associations/join_dependency/join_association.rb
  133. Part 3

  134. Introduce a cache

  135. Cache Code Example cache = ActiveRecord::StatementCache.new do |params| Person.where(name: params.bind).limit(1)

    end ! cache.execute ["Aaron"] cache.execute ["Ebi"] AR::Relation Execute
  136. Binds Cache Object Internals #<ActiveRecord::StatementCache:0x007fb8522a09b0 @bind_map= #<BindMap:0x007fb8522a2490 @bind_values= [[#<SQLite3Column:0x007fb852384f98>, #<Substitute:0x007fb8522c35a0>]],

    @indexes=[0]>, @query_builder= #<Query:0x007fb8522a09d8 @sql= "SELECT \"people\".* FROM \"people\" WHERE \"people\".\"name\" = ? LIMIT 1">> C om piled SQ L
  137. Update Internals, Cache Relation Objects

  138. Things That Use Relations ❤ Post.find() ❤ Post.find_by_* ❤ has_many

    ❤ has_many :through ❤ has_and_belongs_to_many ❤ belongs_to All Cacheable
  139. Example Implementation

  140. Person.find(id) s = find_by_statement_cache.synchronize { find_by_statement_cache[key] ||= StatementCache.create(connection) { |params|

    where(primary_key => params.bind).limit(1) } } record = s.execute([id], self, connection).first R elation! (static) Parameter! (dynamic)
  141. WHAT DO THESE EVEN MEAN??? Ways to call `Post.find` ❤

    Post.find(1) ❤ Post.find([1]) ❤ Post.find([[1]]) ❤ Post.find({1 => 1}) ❤ Post.find(post) ❤ Post.find(1) { … } ❤ scoping { Post.find(1) } Cachea wtf
  142. Rejection Code def find(*ids) # We don't have cache keys

    for this stuff yet return super unless ids.length == 1 return super if block_given? || primary_key.nil? || default_scopes.any? || columns_hash.include?(inheritance_column) || ids.first.kind_of?(Array)
  143. Performance of ActiveRecord

  144. Post.find() Post.find_by_name()

  145. `Post.find` Calls Per Second over Time Calls Per Second (higher

    is better) 0 4000 8000 12000 16000 2-3-stable 3-0-stable 3-1-stable 3-2-stable 4-0-stable 4-1-stable master adequaterecord SQLite3 MySQL2 MySQL PostgreSQL
  146. `Post.find_by_name` Calls Per Second over Time Calls Per Second (higher

    is better) 0 3500 7000 10500 14000 2-3-stable 3-0-stable 3-1-stable 3-2-stable 4-0-stable 4-1-stable master adequaterecord SQLite3 MySQL2 MySQL PostgreSQL
  147. Calls Per Second (MySQL) Calls Per Second (higher is better)

    0 2000 4000 6000 8000 2-3-stable 3-0-stable 3-1-stable 3-2-stable 4-0-stable 4-1-stable master adequaterecord find by id find by name
  148. % Faster than 4-1-stable 0.00% 45.00% 90.00% 135.00% 180.00% SQLite3

    MySQL2 MySQL PostgreSQL find by id find by name
  149. % Faster than 2-3-stable 0.00% 35.00% 70.00% 105.00% 140.00% SQLite3

    MySQL PostgreSQL find by id find by name
  150. Objects Allocated Per Call (find by id) Objects Allocated (lower

    is better) 0 75 150 225 300 2-3-stable 3-0-stable 3-1-stable 3-2-stable 4-0-stable 4-1-stable master adequate SQLite3 MySQL2 MySQL PostgreSQL
  151. Object Allocated Per Call (find by name) Objects Allocated (lower

    is better) 0 75 150 225 300 2-3-stable 3-0-stable 3-1-stable 3-2-stable 4-0-stable 4-1-stable master adequate SQLite3 MySQL2 MySQL PostgreSQL
  152. 70% Fewer Objects Compared to 4-1-stable

  153. 55% Fewer Objects Compared to 2-3-stable

  154. belongs_to

  155. belongs_to calls per second Calls Per Second (higher is better)

    0 3000 6000 9000 12000 2-3-stable 3-0-stable 3-1-stable 3-2-stable 4-0-stable 4-1-stable master adequaterecord SQLite3 MySQL MySQL2 PostgreSQL
  156. belongs_to Percent Faster 0.00% 45.00% 90.00% 135.00% 180.00% SQLite3 MySQL

    MySQL2 PostgreSQL 2-3-stable 4-1-stable
  157. has_many has_many :through

  158. `has_many` Call Speed Over Time Calls Per Second (higher is

    better) 0 2750 5500 8250 11000 2-3-stable 3-0-stable 4-1-stable master adequaterecord SQlite3 MySQL MySQL2 PostgreSQL
  159. `hm:t` Call Speed Over Time Calls Per Second (higher is

    better) 0 3000 6000 9000 12000 2-3-stable 3-0-stable 4-1-stable master adequaterecord SQLite3 MySQL MySQL2 PostgreSQL
  160. Percent Faster than 2-3-stable 0% 25% 50% 75% 100% SQLite3

    MySQL PostgreSQL has_many hm:t
  161. Percent Faster than 4-1-stable 0% 65% 130% 195% 260% SQLite3

    MySQL MySQL2 PostgreSQL has_many hm:t
  162. has_many :through growth

  163. Growth Test class A < ActiveRecord::Base has_many :bs has_many :cs,

    :through => :bs end ! class B < ActiveRecord::Base belongs_to :a has_many :cs end ! class C < ActiveRecord::Base belongs_to :b end
  164. Growth Test class A < ActiveRecord::Base has_many :bs has_many :cs,

    :through => :bs has_many :ds, :through => :cs end ! class B < ActiveRecord::Base belongs_to :a has_many :cs end ! class C < ActiveRecord::Base belongs_to :b has_many :ds end ! class D < ActiveRecord::Base belongs_to :b end as we add more,! what’s the speed?
  165. Time Taken to Call hm:t 100k times Seconds Taken (lower

    is better) 0 40 80 120 160 Nuber of hm:t associations 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 y = 0.5783x + 5.9815 R² = 0.9971 y = 7.448x + 16.506 R² = 0.9974 4-1-stable adequate AWWWWW YEAH, CONSTANT TIME!
  166. TL;DR: ~100% faster

  167. TL;DR: 9001% better

  168. Challenges

  169. Trade Memory For Speed

  170. Total Cache Size = count(find_by_*) * Size

  171. Raw Relations

  172. Example Controller class PeopleController def index @people = Person.where(name: params[:name]).to_a

    end end
  173. Can we cache it?

  174. Experimental Branch Person.where(name: params[:name]).to_a Iterations Per Second (higher is better)

    0 2000 4000 6000 8000 where(:name) Master Experimental Branch
  175. ~30% faster

  176. Key Calculation def cached_query if [joins_values, having_values, group_values, order_values, select_values,

    ].any?(&:any?) return yield end ! if limit_value || offset_value || distinct_value || from_value || lock_value return yield end ! if where_values.length != bind_values.length return yield end ! key = if bind_values.any? "bv_#{bind_values.map { |bv| bv.first.name }.join}" else :find_all_stuff end ! connection = @klass.connection cache = @klass.find_by_statement_cache ! s = cache[key] || cache.synchronize { cache[key] ||= begin bvs = bind_values.map { |bv| bv = bv.dup bv[1] = StatementCache::Substitute.new bv } StatementCache.create_with_binds(connection, arel, bvs) end } s.execute bind_values.map(&:last), @klass, connection end Key Calculation Query Execution
  177. Over 11 variables impact the key

  178. This code only handles two types of queries

  179. One more experiment

  180. Finder Comparison Benchmark.ips do |x| x.report('Person.where') { Person.where(name: 'Aaron').first }

    x.report('Person.find_by') { Person.find_by_name('Aaron') } end
  181. Finder Comparison Calls Per Second 0 3500 7000 10500 14000

    Finder .where() .find_by_name
  182. find_by_name is 3x faster

  183. Finder Comparison Benchmark.ips do |x| x.report('Person.where') { Person.where(name: 'Aaron').first }

    x.report('Person.find_by') { Person.find_by_name('Aaron') } end New AR::Relation Allocation
  184. Should we cache all relations?

  185. I’m not sure.

  186. I want a new API.

  187. New API class Person < ActiveRecord::Base cached_query(:find_all_by_name) do |name| where(name:

    name) end end ! Person.find_all_by_name("foo") Person.find_all_by_name("bar")
  188. Cache Key is Easy

  189. Relation API maintained

  190. Speeding up Helpers

  191. Object Allocation Reduction

  192. Profiling Request / Response

  193. Test Code task :view_stack do app = Ko1TestApp::Application.instance app.app !

    env = rackenv "/books/new" do_test_task(app, env.dup) require 'stackprof' puts "#" * 90 StackProf.run(mode: :cpu, out: 'req-res.dump') do 1800.times { do_test_task(app, env.dup) } end end
  194. Test Code task :view_stack do app = Ko1TestApp::Application.instance app.app !

    env = rackenv "/books/new" do_test_task(app, env.dup) require 'stackprof' puts "#" * 90 StackProf.run(mode: :cpu, out: 'req-res.dump') do 1800.times { do_test_task(app, env.dup) } end end
  195. Test Code task :view_stack do app = Ko1TestApp::Application.instance app.app !

    env = rackenv "/books/new" do_test_task(app, env.dup) require 'stackprof' puts "#" * 90 StackProf.run(mode: :cpu, out: 'req-res.dump') do 1800.times { do_test_task(app, env.dup) } end end
  196. TOTAL (pct) SAMPLES (pct) FRAME 813 (9.5%) 813 (9.5%) ActiveSupport::SafeBuffer#initialize

    699 (8.1%) 350 (4.1%) block in ActiveRecord::Read#read_attribute 486 (5.7%) 298 (3.5%) ActionController::UrlFor#url_options 670 (7.8%) 274 (3.2%) ActionDispatch::Journey::Format#evaluate 773 (9.0%) 253 (2.9%) ActionDispatch#parameterize_args 1172 (13.6%) 220 (2.6%) ActiveRecord::Persistence#instantiate 213 (2.5%) 213 (2.5%) block in SQLite3::Statement#each 208 (2.4%) 208 (2.4%) ActiveSupport::SafeBuffer#html_safe? 204 (2.4%) 204 (2.4%) ActionDispatch::UrlFor#routes_generation? 245 (2.9%) 191 (2.2%) block (2 levels) in Class#class_attribute
  197. ActiveSupport::SafeBuffer #initialize

  198. Finding Calls require 'active_support/all' ! trace = TracePoint.new(:c_call, :call) {

    |tp| if tp.defined_class == ActiveSupport::SafeBuffer && tp.method_id == :initialize puts "#" * 90 puts tp.binding.eval "caller" end } ! trace.enable "asdfadsf".html_safe ActiveSupport::SafeBuffer.new "omgee" Callstack
  199. Output ##################################################################### t1.rb:7:in `eval' t1.rb:7:in `block in <main>' lib/active_support/core_ext/string/output_safety.rb:166:in `initialize'

    lib/active_support/core_ext/string/output_safety.rb:251:in `new' lib/active_support/core_ext/string/output_safety.rb:251:in `html_safe' t1.rb:12:in `<main>' ##################################################################### t1.rb:7:in `eval' t1.rb:7:in `block in <main>' lib/active_support/core_ext/string/output_safety.rb:166:in `initialize' t1.rb:13:in `new' t1.rb:13:in `<main>'
  200. In Rails

  201. Tag Options def tag_option(key, value, escape) if value.is_a?(Array) value =

    escape ? safe_join(value, " ") : value.join(" ") else value = escape ? ERB::Util.h(value) : value end %(#{key}="#{value}") end
  202. HTML Sanitization in Rails

  203. ActiveSupport:: SafeBuffer

  204. Ordinary String >> x = "foo" => "foo" >> x.class

    => String >> x.html_safe? => false
  205. SafeBuffer >> x = "foo" => "foo" >> y =

    x.html_safe => "foo" >> y.class => ActiveSupport::SafeBuffer >> y.html_safe? => true
  206. `html_safe` just tags the string.

  207. ERB::Utils.h

  208. ERB::Utils.h def html_escape(s) s = s.to_s if s.html_safe? s else

    s.gsub(HTML_ESCAPE_REGEXP, HTML_ESCAPE).html_safe end end
  209. Creates 2 Strings.

  210. Tag Options def tag_option(key, value, escape) if value.is_a?(Array) value =

    escape ? safe_join(value, " ") : value.join(" ") else value = escape ? ERB::Util.h(value) : value end %(#{key}="#{value}") end
  211. String -> String -> SafeBuffer -> String

  212. String -> String -> String

  213. Extract Method def unwrapped_html_escape(s) # :nodoc: s = s.to_s if

    s.html_safe? s else s.gsub(HTML_ESCAPE_REGEXP, HTML_ESCAPE) end end ! def html_escape(s) unwrapped_html_escape(s).html_safe end
  214. Update Callers def tag_option(key, value, escape) if value.is_a?(Array) value =

    escape ? safe_join(value, " ") : value.join(" ") else value = escape ? ERB::Util.unwrapped_html_escape(value) : value end %(#{key}="#{value}") end
  215. String -> String -> String

  216. ~200 Allocations Per Request for /books/new

  217. Request Benchmark task :allocation_tracer do app = Ko1TestApp::Application.instance app.app do_test_task(app)

    env = rackenv "/books/new" do_test_task(app, env.dup) ObjectSpace::AllocationTracer.trace do TEST_CNT.times { do_test_task(app, env.dup) } end p ObjectSpace::AllocationTracer.allocated_count_table end "/books/new"
  218. Request Benchmark task :allocation_tracer do app = Ko1TestApp::Application.instance app.app do_test_task(app)

    env = rackenv "/books/new" do_test_task(app, env.dup) ObjectSpace::AllocationTracer.trace do TEST_CNT.times { do_test_task(app, env.dup) } end p ObjectSpace::AllocationTracer.allocated_count_table end
  219. Request Benchmark task :allocation_tracer do app = Ko1TestApp::Application.instance app.app do_test_task(app)

    env = rackenv "/books/new" do_test_task(app, env.dup) ObjectSpace::AllocationTracer.trace do TEST_CNT.times { do_test_task(app, env.dup) } end p ObjectSpace::AllocationTracer.allocated_count_table end
  220. Allocations Per Request 0 275 550 825 1100 T_STRING T_ARRAY

    T_HASH T_NODE T_DATA OTHER 4-0-stable 4-1-stable master
  221. ~19% reduction since 4-0-stable

  222. ~14% reduction since 4-1-stable

  223. YMMV (Your Mileage May Vary)

  224. String Object Reduction

  225. Mutable Strings irb(main):007:0> 5.times { irb(main):008:1* p "foo".object_id irb(main):009:1> }

    70344882872020 70344882871920 70344882871840 70344882871720 70344882871540 => 5 irb(main):010:0>
  226. Frozen Strings irb(main):010:0> 5.times { irb(main):011:1* p "foo".freeze.object_id irb(main):012:1> }

    70344870307760 70344870307760 70344870307760 70344870307760 70344870307760 => 5
  227. ERB Template <% books.each do |book| %> <tr><td> <%= book.name

    %> </tr></td> <% end %>
  228. Compiled Template @output_buffer = output_buffer || ActionView::OutputBuffer.new;@output_buffer.safe_append='<h1>Listing books</h1> ! <table>

    <thead> <tr> <th>Name</th> <th colspan="3"></th> </tr> </thead> ! <tbody> '.freeze; @books.each do |book| @output_buffer.safe_append=' <tr> <td>'.freeze;@output_buffer.append=( book.name );@output_buffer.safe_append='</td> <td>'.freeze;@output_buffer.append=( link_to 'Show', book );@output_buffer.safe_append='</ td> </tr> '.freeze; end @output_buffer.safe_append=' </tbody> </table> ! <br>
  229. Compiled Template @output_buffer = ActionView::OutputBuffer.new @output_buffer.safe_append=' <tr> <td>' @output_buffer.append=( book.name

    ) HTML Literal
  230. Template Literals Can’t Change

  231. Add `freeze` @output_buffer = ActionView::OutputBuffer.new @output_buffer.safe_append=' <tr> <td>’.freeze @output_buffer.append=( book.name

    ) HTML Literal
  232. Allocations Per Request 0 275 550 825 1100 T_STRING T_ARRAY

    T_HASH T_NODE T_DATA OTHER 4-0-stable 4-1-stable master
  233. Speeding up Output

  234. WARNING:! Work in Progress

  235. Law of Demeter

  236. Suggestion of Demeter

  237. None
  238. Arrested Developer?

  239. It’s not about dots, it’s about types.

  240. Fewer Types == Faster / Easier code

  241. Compiled Template @output_buffer = output_buffer || ActionView::OutputBuffer.new;@output_buffer.safe_append='<h1>Listing books</h1> ! <table>

    <thead> <tr> <th>Name</th> <th colspan="3"></th> </tr> </thead> ! <tbody> '.freeze; @books.each do |book| @output_buffer.safe_append=' <tr> <td>'.freeze;@output_buffer.append=( book.name );@output_buffer.safe_append='</td> <td>'.freeze;@output_buffer.append=( link_to 'Show', book );@output_buffer.safe_append='</ td> </tr> '.freeze; end @output_buffer.safe_append=' </tbody> </table> ! <br>
  242. Compiled Template @output_buffer = ActionView::OutputBuffer.new @output_buffer.safe_append=' <tr> <td>'.freeze @output_buffer.append=( book.name

    ) HTML Literal
  243. safe_append= class OutputBuffer def safe_append=(value) return self if value.nil? super(value.to_s)

    end end W hy?
  244. safe_append= class OutputBuffer def safe_append=(value) super(value.to_s) end end

  245. Law of Demeter

  246. Defensive Programming

  247. superclass def safe_append=(value) (!html_safe? || arg.html_safe?) ? arg : ERB::Utils.h(arg)

    end Only on m utations
  248. How can you get the Output Buffer?

  249. Who Mutates the OutputBuffer?

  250. I think no one.

  251. superclass def safe_append=(value) arg.html_safe? ? arg : ERB::Utils.h(arg) end

  252. Cache Invariants

  253. Eliminate Objects

  254. "No code is faster than no code"

  255. Limit Types

  256. Fewer Types = Less Code

  257. Less Code = Faster Code

  258. Report Performance Issues

  259. Rails 4.2 will be the fastest ever!

  260. THANKS!

  261. append= def append=(value) (!html_safe? || arg.html_safe?) ? arg : escape(arg)

    end