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

Brighton Ruby Conf 2014

Brighton Ruby Conf 2014

F29327647a9cff5c69618bae420792ea?s=128

Aaron Patterson

July 22, 2014
Tweet

Transcript

  1. Hello!!!

  2. Welcome!

  3. Thanks!!!

  4. You Beer Me

  5. None
  6. Samuel L. Jackson @tenderlove

  7. None
  8. America

  9. 'MURICA

  10. FREEDOM

  11. None
  12. None
  13. Ruby Core Rails Core

  14. Rack-Core Racc-Core

  15. Psych-Core Fiddle-Core

  16. Nokogiri-Core rails_autolink-Core

  17. Core-Core IHaveNoIdea-Core

  18. ☑Adequate Everything. Adequately.

  19. Twitter: tenderlove GitHub: tenderlove Instagram: tenderlove Yo: tenderlove

  20. ಠ_ಠ ಠ_ಠ Separation Of Concerns

  21. OMG! INTERNET! POINTS

  22. Revert Commits Count Too!

  23. More mistakes == more points!!!!

  24. Short Stack Engineer

  25. None
  26. None
  27. Dad-Joke Programmer

  28. Gorbachev Puff Puff Thunderhorse

  29. SEA-TAC Airport YouTube

  30. None
  31. None
  32. None
  33. Node.JS

  34. Closer to the METAL

  35. None
  36. ENGLAND!!

  37. None
  38. None
  39. None
  40. None
  41. I realize

  42. There is no "d" in "z".

  43. "z" == "zee"

  44. $$$$$$$$ $$$$$$$$ $$$ 'MURICA!

  45. I feel very bad…

  46. @fishermanchips

  47. Brighton Ruby! Recap!

  48. Ruby Order SORTED

  49. Hi, I’m! Samuel L. Jackson!

  50. Better Security

  51. 255.255.255.254 Important!

  52. Cucumber

  53. Steak Holder

  54. How to pick JS libraries

  55. None
  56. None
  57. Quadrant System

  58. Cats Code Beer Adequate System

  59. 1 Quadrant Short.

  60. It’s Adequate.

  61. RubyMotion! is a bridge. (JUST KIDDING!)

  62. My Favorite Type System

  63. Word Perfect 5.1

  64. Yo Dawg, I heard you like Sandi

  65. None
  66. Fizz Buzz?

  67. I am not good enough to be a developer.

  68. If I can developer, you can too!

  69. BANANA!! ANANA!B

  70. I’ve never played Mario Kart. :’(

  71. Strong Ducks?

  72. None
  73. Gave me a run for my money.

  74. Ground Breaking Technology

  75. None
  76. Speed up Rails,! Speed up your Code

  77. Stuff on my mind

  78. Pro Tip:

  79. ruby -d

  80. ruby -d -Ilib:test test/cases/ base_test.rb

  81. Exception `LoadError' at rubygems.rb:1194 - cannot load such file --

    rubygems/defaults/ operating_system ! Exception `LoadError' at kernel_require.rb:55 - cannot load such file -- bundler
  82. Find Swallowed Exceptions!

  83. Rack

  84. def call(env) end LOL Global Data

  85. There will be no Rack 2.0* * There will be

    a Rack 2.0
  86. Ruby >= 2.0?

  87. Next Generation

  88. def call(env, input, output) end IO IO

  89. Just Thoughts.

  90. Performance Tools

  91. Raw Performance

  92. benchmark/ips

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

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

    ( 0.000098)
  95. 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
  96. 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
  97. Set Include: 3047175.3 / sec

  98. Array Include: 3899.2 / sec

  99. IPS: Higher Is Better

  100. 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
  101. 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
  102. 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
  103. 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
  104. 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
  105. IPS Graph 0 1000000 2000000 3000000 4000000 Iterations / Sec

    Set access Hash Access
  106. Blackbox Testing

  107. 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
  108. 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
  109. 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
  110. 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
  111. 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
  112. Real World Example: Routes

  113. 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
  114. 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
  115. 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
  116. 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
  117. 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
  118. 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
  119. 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
  120. 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
  121. 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
  122. 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
  123. Where is time spent?

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

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

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

  127. ================================== 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
  128. GC.stat()

  129. Total allocations GC.stat(:total_allocated_object)

  130. 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
  131. Real World Example: Views

  132. 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"
  133. 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
  134. 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
  135. Test Results Object Allocations Per Request 2000 2150 2300 2450

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

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

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

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

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

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

    ObjectSpace::AllocationTracer.allocated_count_table
  142. 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}
  143. Speeding up Helpers

  144. Object Allocation Reduction

  145. Profiling Request / Response

  146. 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
  147. 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
  148. 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
  149. 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
  150. ActiveSupport::SafeBuffer #initialize

  151. 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
  152. 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>'
  153. In Rails

  154. 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
  155. HTML Sanitization in Rails

  156. ActiveSupport:: SafeBuffer

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

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

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

  160. ERB::Utils.h

  161. 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
  162. Creates 2 Strings.

  163. 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
  164. String -> String -> SafeBuffer -> String

  165. String -> String -> String

  166. 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
  167. 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
  168. String -> String -> String

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

  170. 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"
  171. 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
  172. 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
  173. 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
  174. ~19% reduction since 4-0-stable

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

  176. YMMV (Your Mileage (kilometers?) May Vary)

  177. String Object Reduction

  178. 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>
  179. 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
  180. ERB Template <% books.each do |book| %> <tr><td> <%= book.name

    %> </tr></td> <% end %>
  181. 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>
  182. Compiled Template @output_buffer = ActionView::OutputBuffer.new @output_buffer.safe_append=' <tr> <td>' @output_buffer.append=( book.name

    ) HTML Literal
  183. Template Literals Can’t Change

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

    ) HTML Literal
  185. 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
  186. Speeding up Output

  187. WARNING:! Work in Progress

  188. Law of Demeter

  189. Suggestion of Demeter

  190. None
  191. Arrested Developer?

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

  193. Fewer Types == Faster / Easier code

  194. 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>
  195. Compiled Template @output_buffer = ActionView::OutputBuffer.new @output_buffer.safe_append=' <tr> <td>'.freeze @output_buffer.append=( book.name

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

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

  198. safe_append= class OutputBuffer def safe_append=(value) super(value) end end

  199. safe_append= class OutputBuffer def safe_append=(value) super end end

  200. safe_append= class OutputBuffer end

  201. Law of Demeter

  202. Defensive Programming

  203. Limiting Types

  204. "I only handle strings!"

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

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

  207. Who Mutates the OutputBuffer?

  208. I think no one.

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

  210. Cache Invariants

  211. Eliminate Objects

  212. "No code is faster than no code"

  213. Limit Types

  214. Fewer Types = Less Code

  215. Less Code = Faster Code

  216. Report Performance Issues

  217. Rails 4.2 will be the fastest ever!

  218. THANKS!