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

Brighton Ruby Conf 2014

Sponsored · SiteGround - Reliable hosting with speed, security, and support you can count on.

Brighton Ruby Conf 2014

Avatar for Aaron Patterson

Aaron Patterson

July 22, 2014
Tweet

More Decks by Aaron Patterson

Other Decks in Technology

Transcript

  1. 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
  2. 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
  3. 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
  4. 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
  5. 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
  6. 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
  7. 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
  8. 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
  9. 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
  10. 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
  11. 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
  12. 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
  13. 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
  14. 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
  15. 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
  16. 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
  17. 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
  18. 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
  19. 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
  20. 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
  21. 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
  22. 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
  23. 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
  24. ================================== 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
  25. 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
  26. 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"
  27. 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
  28. 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
  29. Example ObjectSpace::AllocationTracer.trace do 1000.times { ["foo", {}] } end !

    ObjectSpace::AllocationTracer.allocated_count_table
  30. 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
  31. 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
  32. 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
  33. 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
  34. 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
  35. 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>'
  36. 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
  37. Ordinary String >> x = "foo" => "foo" >> x.class

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

    x.html_safe => "foo" >> y.class => ActiveSupport::SafeBuffer >> y.html_safe? => true
  39. 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
  40. 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
  41. 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
  42. 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
  43. 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"
  44. 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
  45. 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
  46. 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
  47. 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>
  48. 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
  49. 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>
  50. 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
  51. 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>