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

Brighton Ruby Conf 2014

Brighton Ruby Conf 2014

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>