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

But At What Cost?

But At What Cost?

Building a template processor for Rails.

Aaron Patterson

May 02, 2019
Tweet

More Decks by Aaron Patterson

Other Decks in Technology

Transcript

  1. doES iT sCaLe?

    View full-size slide

  2. Jubilee Year

    View full-size slide

  3. Look at all the things
    I’m NOT doing!

    View full-size slide

  4. But At What Cost?

    View full-size slide

  5. Aaron Patterson

    View full-size slide

  6. Live From Here

    View full-size slide

  7. I have stickers of my cat!
    (please say "hello")

    View full-size slide

  8. Other == Puns?

    View full-size slide

  9. Speaking of Twitter…

    View full-size slide

  10. ~54% useless

    View full-size slide

  11. March 14, 2019

    View full-size slide

  12. "But At What Cost?"
    Bot

    View full-size slide

  13. Twitter: 100% Useless

    View full-size slide

  14. Response Algorithm
    Is it a pun?
    "Aaron" "But At What
    Cost?"
    Yes No

    View full-size slide

  15. Let’s Practice

    View full-size slide

  16. G GitHub
    We’re Hiring!!!!

    View full-size slide

  17. http://git.io/sparkles

    View full-size slide

  18. Ruby Core Team

    View full-size slide

  19. 10 Years On Ruby Core!
    W
    ow
    !!!

    View full-size slide

  20. Rails Core Team

    View full-size slide

  21. Just Worked™

    View full-size slide

  22. No Boilerplate

    View full-size slide

  23. Java (1.3?) vs Ruby
    List list = new ArrayList();
    list.add("1");
    list.add("2");
    list.add("3");
    Iterator iter = list.iterator();
    List intList = new ArrayList();
    while (iter.hasNext()) {
    String str = (String)iter.next();
    intList.add(Integer.parseInt(str));
    }
    list = %w{ 1 2 3 }
    intList = list.map { |l|
    l.to_i
    }
    Java Ruby

    View full-size slide

  24. My First Ruby Program
    Serious
    ^

    View full-size slide

  25. Never Write
    Infinite Loops

    View full-size slide

  26. Just Worked™

    View full-size slide

  27. No Boilerplate

    View full-size slide

  28. Community Values

    View full-size slide

  29. "I MUST
    WRITE RAILS!"

    View full-size slide

  30. BUT AT WHAT COST?

    View full-size slide

  31. -25%
    $114k ➡ $85k (in 2006)

    View full-size slide

  32. More Companies

    View full-size slide

  33. BUT AT WHAT COST?

    View full-size slide

  34. Game of Thrones

    View full-size slide

  35. Dumbledore Dies

    View full-size slide

  36. Cool Performance Tips

    View full-size slide

  37. No Parenthesis is
    Faster

    View full-size slide

  38. Parens vs No Parens
    def foo1(bar, baz)
    end
    def foo2(bar, baz)
    end
    def foo3(bar, baz)
    end
    def foo1 bar, baz
    end
    def foo2 bar, baz
    end
    def foo3 bar, baz
    end
    Parens No Parens

    View full-size slide

  39. "No code is faster
    than no code"

    View full-size slide

  40. Parse States
    $ ruby --disable-gems -y with-parens.rb | wc -l
    805
    $ ruby --disable-gems -y without-parens.rb | wc -l
    778

    View full-size slide

  41. Single Quotes Are 2x Faster
    Than Double Quotes

    View full-size slide

  42. You Have To Hit The
    Shift Key To Type "

    View full-size slide

  43. Actual Performance Tip
    Rails 6!

    View full-size slide

  44. Allocation Count
    Started GET "/users/new" for ::1 at 2019-04-30 16:45:49 -0500
    Processing by UsersController#new as */*
    Rendering users/new.html.erb within layouts/application
    Rendered users/_form.html.erb (Duration: 1.4ms | Allocations: 552)
    Rendered users/new.html.erb within layouts/application (Duration: 1.7ms | Allocations: 640)
    Completed 200 OK in 6ms (Views: 5.4ms | ActiveRecord: 0.0ms | Allocations: 4140)
    Started GET "/users/new" for ::1 at 2019-04-30 16:47:02 -0500
    Processing by UsersController#new as */*
    Rendering users/new.html.erb within layouts/application
    Rendered users/_form.html.erb (Duration: 1.4ms | Allocations: 827)
    Rendered users/_expensive.html.erb (Duration: 1.6ms | Allocations: 10098)
    Rendered users/new.html.erb within layouts/application (Duration: 4.3ms | Allocations: 11553)
    Completed 200 OK in 9ms (Views: 8.5ms | ActiveRecord: 0.0ms | Allocations: 15981)

    View full-size slide

  45. Allocation Count
    Started GET "/users/new" for ::1 at 2019-04-30 16:45:49 -0500
    Processing by UsersController#new as */*
    Rendering users/new.html.erb within layouts/application
    Rendered users/_form.html.erb (Duration: 1.4ms | Allocations: 552)
    Rendered users/new.html.erb within layouts/application (Duration: 1.7ms | Allocations: 640)
    Completed 200 OK in 6ms (Views: 5.4ms | ActiveRecord: 0.0ms | Allocations: 4140)
    Started GET "/users/new" for ::1 at 2019-04-30 16:47:02 -0500
    Processing by UsersController#new as */*
    Rendering users/new.html.erb within layouts/application
    Rendered users/_form.html.erb (Duration: 1.4ms | Allocations: 827)
    Rendered users/_expensive.html.erb (Duration: 1.6ms | Allocations: 10098)
    Rendered users/new.html.erb within layouts/application (Duration: 4.3ms | Allocations: 11553)
    Completed 200 OK in 9ms (Views: 8.5ms | ActiveRecord: 0.0ms | Allocations: 15981)

    View full-size slide

  46. Rails at GitHub

    View full-size slide

  47. Template Pre-Compilation
    Method Name Generation

    View full-size slide

  48. Template Pre-
    Compilation

    View full-size slide

  49. Method Name
    Generation

    View full-size slide

  50. Build a Template
    Renderer!

    View full-size slide

  51. Rails is MVC

    View full-size slide

  52. MVC
    Model View
    Controller
    Request

    View full-size slide

  53. Modal View Controller

    View full-size slide

  54. MVC
    Model View
    Controller
    Request
    Action Controller
    Action View
    Active Record

    View full-size slide

  55. Action View
    Finds Templates
    Compiles Templates
    Executes Views

    View full-size slide

  56. View Templates

    View full-size slide

  57. Developed by @m_seki

    View full-size slide

  58. ERB Template
    New User
    <%= render_form %>
    <%= link_to 'Back' %>
    new.html.erb

    View full-size slide

  59. ERB Compiler
    require "erb"
    # Compile the template
    template = ERB.new File.read "test.html.erb"
    # Print the template source
    puts template.src
    # Evaluate the template
    puts template.result(binding)
    compile.rb

    View full-size slide

  60. Compiled Template
    _erbout = +""
    _erbout << "New User\n\n".freeze
    _erbout << render_form.to_s
    _erbout << "\n\n".freeze
    _erbout << link_to("Back").to_s
    _erbout << "\n".freeze
    _erbout

    View full-size slide

  61. Modified Compile Script
    require "erb"
    def render_form
    ""
    end
    def link_to(text)
    "#{text}"
    end
    # Compile the template
    template = ERB.new File.read "test.html.erb"
    # Evaluate the template
    puts template.result(binding)

    View full-size slide

  62. Compiler Output
    $ ruby compiler.rb
    New User

    Back

    View full-size slide

  63. ERB Translations
    ERB Source Translated Ruby
    <%= something %> _erbout << something.to_s
    <% something_else %> something_else

    View full-size slide

  64. Capturing Blocks

    <%= capture do %>
    Hello!
    <% end %>

    require "erb"
    def capture
    yield
    end
    # Compile the template
    template = ERB.new File.read "test.html.erb"
    puts template.result(binding)

    View full-size slide

  65. What is the output?
    Hello!



    Hello!

    ERROR!

    View full-size slide

  66. Compiled Source
    _erbout = +''
    _erbout << "\n ".freeze
    _erbout << ( capture do ).to_s
    _erbout << "\n Hello!\n ".freeze
    end
    _erbout << "\n\n".freeze
    _erbout

    <%= capture do %>
    Hello!
    <% end %>

    View full-size slide

  67. Compiled Source
    _erbout = +''
    _erbout << "\n ".freeze
    _erbout << ( capture do ).to_s
    _erbout << "\n Hello!\n ".freeze
    end
    _erbout << "\n\n".freeze
    _erbout

    <%= capture do %>
    Hello!
    <% end %>

    View full-size slide

  68. How does this work in
    Rails?

    View full-size slide

  69. ERB Compiler
    BLOCK_EXPR = /\s*((\s+|\))do|\{)(\s*\|[^|]*\|)?\s*\Z/
    def add_expression(indicator, code)
    flush_newline_if_pending(src)
    if (indicator == "==") || @escape
    src << "@output_buffer.safe_expr_append="
    else
    src << "@output_buffer.append="
    end
    if BLOCK_EXPR.match?(code)
    src << " " << code
    else
    src << "(" << code << ");"
    end
    end

    View full-size slide

  70. ERB in Rails

    View full-size slide

  71. Different ERB Flavors
    Original ERB
    ERubis
    ERubi

    View full-size slide

  72. ERB Performance

    View full-size slide

  73. Rendering Speed
    require "erb"
    require "benchmark/ips"
    class AaronView
    TEMPLATES = { }
    TEMPLATES["index"] = <<-eerb


    Hello <%= name %>

    eerb
    def name; "Aaron"; end
    def render(template)
    ERB.new(TEMPLATES[template]).result(binding)
    end
    end
    view = AaronView.new
    Benchmark.ips do |x|
    x.report("render") { view.render("index") }
    end

    View full-size slide

  74. Rendering Speed
    $ ruby rendering_speed.rb
    Warming up --------------------------------------
    render 2.696k i/100ms
    Calculating -------------------------------------
    render 27.117k (± 5.9%) i/s - 137.496k in 5.092633s

    View full-size slide

  75. Cache Compilation
    class AaronView
    TEMPLATES = { }
    TEMPLATES["index"] = <<-eerb


    Hello <%= name %>

    eerb
    def name; "Aaron"; end
    def render(template)
    ERB.new(TEMPLATES[template]).result(binding)
    end
    COMPILED_TEMPLATES = { }
    def render_compiled(template)
    erb = COMPILED_TEMPLATES[template] ||= ERB.new(TEMPLATES[template])
    erb.result(binding)
    end
    end
    view = AaronView.new
    Benchmark.ips do |x|
    x.report("render") { view.render("index") }
    x.report("render compiled") { view.render_compiled("index") }
    x.compare!
    end

    View full-size slide

  76. Cache Compilation
    $ ruby rendering_speed.rb
    Warming up --------------------------------------
    render 2.709k i/100ms
    render compiled 6.293k i/100ms
    Calculating -------------------------------------
    render 27.115k (± 2.6%) i/s - 138.159k in 5.098881s
    render compiled 63.645k (± 3.9%) i/s - 320.943k in 5.050544s
    Comparison:
    render compiled: 63645.5 i/s
    render: 27114.8 i/s - 2.35x slower

    View full-size slide

  77. Define a Method
    class AaronView
    TEMPLATES = { }
    TEMPLATES["index"] = <<-eerb


    Hello <%= name %>

    eerb
    def name; "Aaron"; end
    def render(template)
    ERB.new(TEMPLATES[template]).result(binding)
    end
    COMPILED_TEMPLATES = { }
    def render_compiled(template)
    erb = COMPILED_TEMPLATES[template] ||= ERB.new(TEMPLATES[template])
    erb.result(binding)
    end
    METHODS_DEFINED = { }
    def render_with_method(template)
    unless METHODS_DEFINED.key? template
    erb = ERB.new(TEMPLATES[template])
    self.class.class_eval "def render_#{template}; #{erb.src}; end"
    METHODS_DEFINED[template] = true
    end
    send "render_#{template}"
    end
    end

    View full-size slide

  78. Define a Method
    $ ruby rendering_speed.rb
    Warming up --------------------------------------
    render 2.750k i/100ms
    render compiled 6.462k i/100ms
    render method 114.420k i/100ms
    Calculating -------------------------------------
    render 28.389k (± 2.7%) i/s - 143.000k in 5.041158s
    render compiled 65.860k (± 2.4%) i/s - 329.562k in 5.006962s
    render method 1.477M (± 1.7%) i/s - 7.437M in 5.038304s
    Comparison:
    render method: 1476591.3 i/s
    render compiled: 65860.1 i/s - 22.42x slower
    render: 28388.6 i/s - 52.01x slower

    View full-size slide

  79. Method Generation
    require "erb"
    erb_source = <<-erb


    Hello <%= name %>

    erb
    erb = ERB.new erb_source
    template = "index"
    self.class.class_eval "def render_#{template}; #{erb.src}; end"
    send "render_#{template}"
    Template
    Source
    Compile
    Source
    Define
    a
    m
    ethod
    Render the
    Template

    View full-size slide

  80. Method Generation
    def render_index
    _erbout = +''
    _erbout << "\n \n Hello ".freeze
    _erbout << name.to_s
    _erbout << "\n\n".freeze
    _erbout
    end
    Compiled Source

    View full-size slide

  81. Templates Are
    Translated to Methods

    View full-size slide

  82. Rails Template Methods

    This is an inner template
    <% puts methods.grep(/_app/) %>

    Template in Rails

    View full-size slide

  83. Rails Template Methods
    Console Output
    _app_views_users_index_html_erb___4485674087862414886_70282093274980
    _app_views_users__first_ja_html_erb__657974686689484881_70282053324000
    _app_views_users__second_html_erb___3508242123539422948_70282053523460

    View full-size slide

  84. Memory Size
    def render_index
    _erbout = +''
    _erbout << "\n \n Hello ".freeze
    _erbout << name.to_s
    _erbout << "\n\n".freeze
    _erbout
    end
    require "objspace"
    iseq = RubyVM::InstructionSequence.of(
    method(:render_index))
    p ObjectSpace.memsize_of iseq
    $ ruby test.rb
    1264

    View full-size slide

  85. Template Handling
    In Rails

    View full-size slide

  86. Instance Variable Visibility
    This is template Foo!
    <%= @some_ivar %>
    This is template Bar!
    <%= @some_ivar %>
    _foo.html.erb _bar.html.erb

    View full-size slide

  87. Compiled Templates
    class ActionView::Base
    def render_foo
    _erbout = +''
    _erbout << "This is template Foo!\n".freeze
    _erbout << @some_ivar.to_s
    _erbout << "\n".freeze
    _erbout
    end
    def render_bar
    _erbout = +''
    _erbout << "This is template Barr!\n".freeze
    _erbout << @some_ivar.to_s
    _erbout << "\n".freeze
    _erbout
    end
    end
    _foo.html.erb
    _bar.html.erb
    Same Instance
    Same Instance

    View full-size slide

  88. Don’t use instance
    variables in your templates

    View full-size slide

  89. "Don’t use instance variables
    in your templates" - Aaron

    View full-size slide

  90. Instance Variable Visibility
    This is template Foo!
    <%= @some_ivar %>
    This is template Bar!
    <%= @some_ivar %>
    _foo.html.erb _bar.html.erb

    View full-size slide

  91. Local Variables

    View full-size slide

  92. Rendering With Locals

    <%= render "content", locals: { name: "Gorby" } %>

    View full-size slide

  93. Compiling With Locals

    Hello, <%= name %>

    def render_content
    _erbout = +''
    _erbout << "\n Hello, ".freeze
    _erbout << name.to_s
    _erbout << "\n\n".freeze
    _erbout
    end
    content.html.erb Compiled Method
    Method Call

    View full-size slide

  94. Preamble With Locals

    View full-size slide

  95. Compiling With Locals

    Hello, <%= name %>

    def render_content(locals)
    # Locals preamble
    name = locals[:name]
    _erbout = +''
    _erbout << "\n Hello, ".freeze
    _erbout << name.to_s
    _erbout << "\n\n".freeze
    _erbout
    end
    content.html.erb Compiled Method

    View full-size slide

  96. Templates Require Context

    <%= render "content",
    locals: { name: "Gorby" } %>
    <%= render "content" %>


    Hello, <%= name %>

    main.html.erb content.html.erb
    Method call or
    local variable?
    local variable
    method call

    View full-size slide

  97. Templates Can’t Be Compiled
    In Advance*
    *(FORESHADOWING)

    View full-size slide

  98. Compiled Too Many
    Times

    View full-size slide

  99. Too Many Compilations

    <%= render "content",
    locals: { name: "Gorby" } %>
    <%= render "content",
    locals: { name: "Gorby",
    friend: "Aaron" } %>

    def render_content_1(locals)
    # Locals preamble
    name = locals[:name]
    _erbout = +''
    _erbout << "\n Hello, ".freeze
    _erbout << name.to_s
    _erbout << "\n\n".freeze
    _erbout
    end
    def render_content_2(locals)
    # Locals preamble
    name = locals[:name]
    friend = locals[:friend]
    _erbout = +''
    _erbout << "\n Hello, ".freeze
    _erbout << name.to_s
    _erbout << "\n\n".freeze
    _erbout
    end
    main.html.erb Compiled "content"
    Unique Preambles

    View full-size slide

  100. "This is nice, but
    what should I do?"

    View full-size slide

  101. Always pass the same
    locals to individual templates.

    View full-size slide

  102. The render function

    View full-size slide

  103. Finds a Template
    Compiles the Template
    Calculates a Method Name
    Calls the Method

    View full-size slide

  104. Find a Template
    (from the cache)
    Compiled?
    Compile the Template
    Call the method
    Calculate a Method
    Name

    View full-size slide

  105. Finding a Template

    View full-size slide

  106. Template Rendering
    Depends on "Requested
    Format"

    View full-size slide

  107. Users Controller
    class UsersController < ApplicationController
    # GET /users
    # GET /users.json
    def index
    @users = User.all
    render "index"
    end
    end
    $ tree app/views/users
    app/views/users
    !"" index.html.erb
    #"" index.xml.erb
    Controller app/views/users/*

    View full-size slide

  108. Template Source
    XML template Users
    index.xml.erb index.html.erb

    View full-size slide

  109. Requests and Rendering
    Request Response Template Rendered
    curl -H 'Accept:
    application/xml' http://
    localhost:3000/users
    XML template index.xml.erb
    curl http://localhost:
    3000/users
    Users index.html.erb
    curl -H 'Accept: text/
    html' http://localhost:
    3000/users
    Users index.html.erb

    View full-size slide

  110. Template Cache Keys
    • Local variables
    • Format
    • Locale
    • Variant (iPhone, etc)

    View full-size slide

  111. Strange Render
    Behavior

    View full-size slide

  112. Users Controller
    class UsersController < ApplicationController
    # GET /users
    # GET /users.json
    def index
    @users = User.all
    render "index"
    end
    end
    $ tree app/views/users
    app/views/users
    !"" _my_template.png.erb
    !"" _my_template.xml.erb
    #"" index.html.erb
    Controller app/views/users/*

    View full-size slide

  113. Template Contents
    Users
    <%= render "my_template" %>
    I guess this is a png?
    index.html.erb _my_template.png.erb

    XML is cool!

    _my_template.xml.erb

    View full-size slide

  114. Requests and Rendering
    Request Response Template Rendered
    curl -H 'Accept: application/xml'
    http://localhost:3000/users
    Missing XML template Error None
    curl -H 'Accept: text/html'
    http://localhost:3000/users
    Missing HTML partial Error None
    Browser Missing HTML partial Error None
    curl http://localhost:3000/users
    Users

    I guess this is a png?
    index.html.erb

    _my_template.png.erb

    View full-size slide

  115. Requests and Rendering
    Request Response Template Rendered
    curl -H 'Accept: application/xml'
    http://localhost:3000/users
    Missing XML template Error None
    curl -H 'Accept: text/html'
    http://localhost:3000/users
    Missing HTML partial Error None
    Browser Missing HTML partial Error None
    curl http://localhost:
    3000/users
    Users
    I guess this is a png?
    index.html.erb
    _my_template.png.erb

    View full-size slide

  116. We Can’t Predict
    Users
    <%= render "my_template" %>

    View full-size slide

  117. respond_to Controller
    class UsersController < ApplicationController
    # GET /users
    # GET /users.json
    def index
    @users = User.all
    render "index"
    end
    end
    class UsersController < ApplicationController
    # GET /users
    # GET /users.json
    def index
    @users = User.all
    respond_to do |format|
    format.html do
    render "index"
    end
    end
    end
    end
    "Bare" render respond_to render

    View full-size slide

  118. Requests and Rendering
    Request Response Template Rendered
    curl -H 'Accept: application/
    xml' http://localhost:3000/
    users
    Missing XML template Error None
    curl -H 'Accept: text/html'
    http://localhost:3000/users
    Missing HTML partial Error None
    Browser Missing HTML partial Error None
    curl http://localhost:3000/
    users
    Missing HTML partial Error None

    View full-size slide

  119. Context Dependent
    Users
    <%= render "my_template" %>
    class UsersController < ApplicationController
    # GET /users
    # GET /users.json
    def index
    @users = User.all
    render "index"
    end
    end
    class UsersController < ApplicationController
    # GET /users
    # GET /users.json
    def index
    @users = User.all
    respond_to do |format|
    format.html do
    render "index"
    end
    end
    end
    end
    index.html.erb

    View full-size slide

  120. Templates are not
    "Context Free"

    View full-size slide

  121. Prediction and
    Consistency

    View full-size slide

  122. Find a Template
    (from the cache)
    Compiled?
    Compile the Template
    Call the method
    Calculate a Method
    Name
    Cheap!
    Cheap!
    Expensive!

    View full-size slide

  123. What Method Will This Call?
    Users
    <%= render "my_template" %>
    Users
    <%= render_my_template_00011123abf %>
    index.html.erb translated template
    Calls a unique method.
    But what method?

    View full-size slide

  124. Find a Template
    (from the cache)
    Compiled?
    Compile the Template
    Call the method
    Calculate a Method
    Name
    Cheap!
    Cheap!
    Expensive!

    View full-size slide

  125. Slower Production Boot
    (but could be eliminated by BootSnap)

    View full-size slide

  126. Lower Memory

    View full-size slide

  127. Faster Runtime
    No More "Cache Check" Penalty

    View full-size slide

  128. Three Templates

    <%= render partial: "customer",
    collection: customers %>


    <%= render partial: "customer",
    collection: customers,
    cached: true %>

    _col.html.erb _cached_col.html.erb

    Hello: <%= customer.name %>

    _customer.html.erb

    View full-size slide

  129. Benchmark
    https://github.com/rails/rails/issues/35257

    View full-size slide

  130. Results
    $ be ruby render_benchmark.rb
    -- create_table(:customers, {:force=>true})
    -> 0.0108s
    <#ActiveSupport::Cache::MemoryStore entries=1000, size=333893, options={}>
    Warming up --------------------------------------
    collection render: no cache
    6.000 i/100ms
    collection render: with cache
    1.000 i/100ms
    Calculating -------------------------------------
    collection render: no cache
    65.319 (± 7.7%) i/s - 330.000 in 5.080651s
    collection render: with cache
    11.504 (± 8.7%) i/s - 58.000 in 5.071030s
    Comparison:
    collection render: no cache: 65.3 i/s
    collection render: with cache: 11.5 i/s - 5.68x slower

    View full-size slide

  131. I was convinced
    the cache didn’t work

    View full-size slide

  132. I was convinced
    the cache didn’t work

    View full-size slide

  133. I was convinced
    the cache didn’t work

    View full-size slide

  134. I was convinced
    the cache didn’t work

    View full-size slide

  135. But, there were entries
    in the cache

    View full-size slide

  136. BUT AT WHAT COST?

    View full-size slide

  137. Profiled The Cache
    ==================================
    Mode: wall(1000)
    Samples: 4690 (0.00% miss rate)
    GC: 207 (4.41%)
    ==================================
    TOTAL (pct) SAMPLES (pct) FRAME
    594 (12.7%) 594 (12.7%) ActiveModel::LazyAttributeHash#[]
    387 (8.3%) 387 (8.3%) ActiveRecord::Transactions#update_attributes_from_transaction_state
    1199 (25.6%) 246 (5.2%) ActiveSupport::Cache::Store#expanded_key
    301 (6.4%) 218 (4.6%) AbstractController::Caching::Fragments#combined_fragment_cache_key
    207 (4.4%) 207 (4.4%) (garbage collection)
    932 (19.9%) 196 (4.2%) ActionView::CollectionCaching#collection_by_cache_keys
    450 (9.6%) 181 (3.9%) ActiveSupport::Cache::Store#expanded_version

    View full-size slide

  138. Cache Key Calculation
    Was More Expensive
    Than Template Execution

    View full-size slide

  139. Cache Payoff

    Hello: <%= customer.name %>

    Hello: <%= customer.name %>
    Hello: <%= customer.name %>
    Hello: <%= customer.name %>
    Hello: <%= customer.name %>
    Hello: <%= customer.name %>
    Hello: <%= customer.name %>
    Hello: <%= customer.name %>
    Hello: <%= customer.name %>
    Hello: <%= customer.name %>
    Hello: <%= customer.name %>
    Hello: <%= customer.name %>
    Hello: <%= customer.name %>
    Hello: <%= customer.name %>
    Hello: <%= customer.name %>
    Hello: <%= customer.name %>
    Hello: <%= customer.name %>
    Hello: <%= customer.name %>
    Hello: <%= customer.name %>
    Hello: <%= customer.name %>
    Hello: <%= customer.name %>
    Hello: <%= customer.name %>
    Hello: <%= customer.name %>
    Old Template New Template

    View full-size slide

  140. Benchmark Results
    $ be ruby render_benchmark.rb
    -- create_table(:customers, {:force=>true})
    -> 0.0075s
    <#ActiveSupport::Cache::MemoryStore entries=1000, size=900893, options={}>
    Warming up --------------------------------------
    collection render: no cache
    4.000 i/100ms
    collection render: with cache
    4.000 i/100ms
    Calculating -------------------------------------
    collection render: no cache
    41.066 (± 2.4%) i/s - 208.000 in 5.070051s
    collection render: with cache
    43.192 (± 4.6%) i/s - 216.000 in 5.012803s
    Comparison:
    collection render: with cache: 43.2 i/s
    collection render: no cache: 41.1 i/s - same-ish: difference falls within error

    View full-size slide

  141. "Cache" doesn’t mean
    "fast"

    View full-size slide

  142. Speeding Up Templates

    View full-size slide

  143. Template Static
    Analysis

    View full-size slide

  144. Pre-Compiling
    Templates
    https://github.com/jhawthorn/actionview_precompiler/

    View full-size slide

  145. ActionviewPrecompiler.precompile

    View full-size slide

  146. Static Analysis
    <%= render "no_locals" %>
    <%= render "static_locals", locals: { foo: bar } %>
    <%= render "dynamic_locals", locals: { foo:
    bar }.merge(baz) %>
    Can Precompile!
    Can Precompile!
    Nope!

    View full-size slide

  147. Predicting "Render"
    Methods

    View full-size slide

  148. "Same Format Assumption"
    Users
    <%= render "my_template" %>
    <%= render "my_template", format: :png %>
    $ tree app/views/users
    app/views/users
    !"" _my_template.html.erb
    !"" _my_template.png.erb
    !"" _my_template.xml.erb
    #"" index.html.erb
    index.html.erb templates

    View full-size slide

  149. Ambiguous Render Problem
    Users
    <%= render "my_template" %>
    <%= render "my_template", format: :html %>
    $ tree app/views/users
    app/views/users
    !"" _my_template.png.erb
    !"" _my_template.xml.erb
    #"" index.html.erb
    index.html.erb templates
    Sometimes an exception
    Sometimes a PNG

    Always an exception

    View full-size slide

  150. "Always Optimize"
    Users
    <%= render "my_template" %>
    <%= render "my_template", format: :html %>
    Users
    <%= render "my_template", format: :png %>
    <%= render "my_template", format: :html %>
    index.html.erb index.html.erb

    View full-size slide

  151. "Same Format Assumption"
    Users
    <%= render "my_template" %>
    <%= render "my_template", format: :png %>
    $ tree app/views/users
    app/views/users
    !"" _my_template.html.erb
    !"" _my_template.png.erb
    !"" _my_template.xml.erb
    #"" index.html.erb
    index.html.erb templates

    View full-size slide

  152. "Same Format Assumption"
    Users
    <%= render "my_template" %>
    <%= render "my_template", format: :png %>
    Users
    <%= render_my_template_html_00123abc %>
    <%= render_my_template_png_00123abc %>
    index.html.erb Optimized Template

    View full-size slide

  153. Ambiguous Render Problem
    Users
    <%= render "my_template" %>
    <%= render "my_template", format: :html %>
    $ tree app/views/users
    app/views/users
    !"" _my_template.png.erb
    !"" _my_template.xml.erb
    #"" index.html.erb
    index.html.erb templates

    View full-size slide

  154. Ambiguous Render Problem
    Users
    <%= render "my_template" %>
    <%= render "my_template", format: :html %>
    Users
    <%= render "my_template" %>
    <%= raise MissingTemplateError %>
    index.html.erb Optimized Template

    View full-size slide

  155. Be "Context Free"
    Don’t depend on side effects

    View full-size slide

  156. Be Consistent

    View full-size slide

  157. Cache != Fast

    View full-size slide

  158. Benchmark First!

    View full-size slide

  159. Keep Building Apps!

    View full-size slide

  160. Thank You!!!

    View full-size slide