Slide 1

Slide 1 text

The View Is Clear from Here

Slide 2

Slide 2 text

Aaron Patterson

Slide 3

Slide 3 text

@tenderlove

Slide 4

Slide 4 text

No content

Slide 5

Slide 5 text

No content

Slide 6

Slide 6 text

No content

Slide 7

Slide 7 text

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

Slide 8

Slide 8 text

No content

Slide 9

Slide 9 text

No content

Slide 10

Slide 10 text

Ruby Core Team

Slide 11

Slide 11 text

Rails Core Team

Slide 12

Slide 12 text

G GitHub

Slide 13

Slide 13 text

Le Git

Slide 14

Slide 14 text

git push -f

Slide 15

Slide 15 text

git checkout -b

Slide 16

Slide 16 text

git cherry-pick

Slide 17

Slide 17 text

No content

Slide 18

Slide 18 text

Happy GPS Rollover Day! April 6, 23:59:42 UTC I guess this is actually tomorrow……

Slide 19

Slide 19 text

GPS Uses 10 bits!

Slide 20

Slide 20 text

Single Quotes Are 2x Faster Than Double Quotes

Slide 21

Slide 21 text

You Have To Hit The Shift Key To Type "

Slide 22

Slide 22 text

Logic AND OR NOT A

Slide 23

Slide 23 text

Гематоген

Slide 24

Slide 24 text

"атоген"

Slide 25

Slide 25 text

gem 'атоген' Gemfile

Slide 26

Slide 26 text

The View Is Clear from Here

Slide 27

Slide 27 text

WARNING!@ There Will Be Code

Slide 28

Slide 28 text

There Will Be Ruby

Slide 29

Slide 29 text

There Will Be Rails

Slide 30

Slide 30 text

Software Engineering Practices

Slide 31

Slide 31 text

Rails is MVC

Slide 32

Slide 32 text

MVC Model View Controller

Slide 33

Slide 33 text

MVC Model View Controller Request

Slide 34

Slide 34 text

MVC Model View Controller Request Action Controller Action View Active Record

Slide 35

Slide 35 text

Action View

Slide 36

Slide 36 text

Action View Finds Templates Compiles Templates Executes Views

Slide 37

Slide 37 text

Finding Templates

Slide 38

Slide 38 text

View Templates

Slide 39

Slide 39 text

ERB

Slide 40

Slide 40 text

Developed by @m_seki

Slide 41

Slide 41 text

ERB Template

New User

<%= render_form %> <%= link_to 'Back' %> new.html.erb

Slide 42

Slide 42 text

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

Slide 43

Slide 43 text

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

Slide 44

Slide 44 text

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)

Slide 45

Slide 45 text

Compiler Output $ ruby compiler.rb

New User

Back

Slide 46

Slide 46 text

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

Slide 47

Slide 47 text

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)

Slide 48

Slide 48 text

What is the output? Hello!
Hello!
ERROR!

Slide 49

Slide 49 text

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 %>

Slide 50

Slide 50 text

How does this work in Rails?

Slide 51

Slide 51 text

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

Slide 52

Slide 52 text

ERB in Rails

Slide 53

Slide 53 text

Different ERB Flavors Original ERB ERubis ERubi

Slide 54

Slide 54 text

ERB Performance

Slide 55

Slide 55 text

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 $ ruby rendering_speed.rb Warming up -------------------------------------- render 2.696k i/100ms Calculating ------------------------------------- render 27.117k (± 5.9%) i/s - 137.496k in 5.092633s

Slide 56

Slide 56 text

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 $ 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

Slide 57

Slide 57 text

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 $ 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

Slide 58

Slide 58 text

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}" def render_index _erbout = +'' _erbout << "\n \n

Hello ".freeze _erbout << name.to_s _erbout << "

\n\n".freeze _erbout end Template Source Compile Source Define a m ethod Render the Tem plate Compiled Source

Slide 59

Slide 59 text

Templates Are Translated to Methods

Slide 60

Slide 60 text

Rails Template Methods

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

Template in Rails 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

Slide 61

Slide 61 text

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 Measuring Memory Console Output

Slide 62

Slide 62 text

Template Handling In Rails

Slide 63

Slide 63 text

Instance Variable Visibility

This is template Foo!

<%= @some_ivar %>

This is template Bar!

<%= @some_ivar %> _foo.html.erb _bar.html.erb

Slide 64

Slide 64 text

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

Slide 65

Slide 65 text

Don’t use instance variables in your templates

Slide 66

Slide 66 text

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

Slide 67

Slide 67 text

Instance Variable Visibility

This is template Foo!

<%= @some_ivar %>

This is template Bar!

<%= @some_ivar %> _foo.html.erb _bar.html.erb

Slide 68

Slide 68 text

Local Variables

Slide 69

Slide 69 text

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

Slide 70

Slide 70 text

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

Slide 71

Slide 71 text

Preamble With Locals

Slide 72

Slide 72 text

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

Slide 73

Slide 73 text

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

Slide 74

Slide 74 text

Templates Can’t Be Compiled In Advance

Slide 75

Slide 75 text

Compiled Too Many Times

Slide 76

Slide 76 text

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

Slide 77

Slide 77 text

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

Slide 78

Slide 78 text

Always pass the same locals to individual templates.

Slide 79

Slide 79 text

The render function

Slide 80

Slide 80 text

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

Slide 81

Slide 81 text

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

Slide 82

Slide 82 text

Finding a Template

Slide 83

Slide 83 text

Template Rendering Depends on "Requested Format"

Slide 84

Slide 84 text

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/*

Slide 85

Slide 85 text

Template Source

XML template

Users

index.xml.erb index.html.erb

Slide 86

Slide 86 text

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

Slide 87

Slide 87 text

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

Slide 88

Slide 88 text

Strange Render Behavior

Slide 89

Slide 89 text

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/*

Slide 90

Slide 90 text

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

Slide 91

Slide 91 text

Requests and Rendering Request Response Template Rendered curl -H 'Accept: application/xml' http://localhost:3000/users Missing XML template Error None curl http://localhost:3000/users

Users

I guess this is a png? index.html.erb _my_template.png.erb curl -H 'Accept: text/html' http:// localhost:3000/users Missing HTML partial Error None Browser Missing HTML partial Error None

Slide 92

Slide 92 text

Error!!

Slide 93

Slide 93 text

We Can’t Predict

Users

<%= render "my_template" %>

Slide 94

Slide 94 text

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

Slide 95

Slide 95 text

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

Slide 96

Slide 96 text

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

Slide 97

Slide 97 text

Templates are not "Context Free"

Slide 98

Slide 98 text

Prediction and Consistency

Slide 99

Slide 99 text

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

Slide 100

Slide 100 text

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?

Slide 101

Slide 101 text

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

Slide 102

Slide 102 text

Slower Production Boot (but could be eliminated by BootSnap)

Slide 103

Slide 103 text

Lower Memory

Slide 104

Slide 104 text

Faster Runtime No More "Cache Check" Penalty

Slide 105

Slide 105 text

DETOUR!

Slide 106

Slide 106 text

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

    Slide 107

    Slide 107 text

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

    Slide 108

    Slide 108 text

    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

    Slide 109

    Slide 109 text

    I was convinced the cache didn’t work

    Slide 110

    Slide 110 text

    I was convinced the cache didn’t work

    Slide 111

    Slide 111 text

    I was convinced the cache didn’t work

    Slide 112

    Slide 112 text

    I was convinced the cache didn’t work

    Slide 113

    Slide 113 text

    But, there were entries in the cache

    Slide 114

    Slide 114 text

    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

    Slide 115

    Slide 115 text

    Cache Key Calculation Was More Expensive Than Template Execution

    Slide 116

    Slide 116 text

    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

    Slide 117

    Slide 117 text

    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

    Slide 118

    Slide 118 text

    "Cache" doesn’t mean "fast"

    Slide 119

    Slide 119 text

    Making Render Fast

    Slide 120

    Slide 120 text

    Make Render Usually Fast

    Slide 121

    Slide 121 text

    "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

    Slide 122

    Slide 122 text

    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

    Slide 123

    Slide 123 text

    "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

    Slide 124

    Slide 124 text

    "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

    Slide 125

    Slide 125 text

    "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

    Slide 126

    Slide 126 text

    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

    Slide 127

    Slide 127 text

    Ambiguous Render Problem

    Users

    <%= render "my_template" %> <%= render "my_template", format: :html %>

    Users

    <%= render "my_template" %> <%= raise MissingTemplateError %> index.html.erb Optimized Template

    Slide 128

    Slide 128 text

    Wrap It Up

    Slide 129

    Slide 129 text

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

    Slide 130

    Slide 130 text

    Be Consistent

    Slide 131

    Slide 131 text

    Cache != Fast

    Slide 132

    Slide 132 text

    Benchmark First!

    Slide 133

    Slide 133 text

    Make Your Locals Match

    Slide 134

    Slide 134 text

    Thank You!!!