Skip to content

Commit

Permalink
Speed up partial rendering by caching "variable" calculation
Browse files Browse the repository at this point in the history
This commit speeds up rendering partials by caching the variable name
calculation on the template.  The variable name is based on the "virtual
path" used for looking up the template.  The same virtual path
information lives on the template, so we can just ask the cached
template object for the variable.

This benchmark takes a couple files, so I'll cat them below:

```
[aaron@TC ~/g/r/actionview (speed-up-partials)]$ cat render_benchmark.rb
require "benchmark/ips"
require "action_view"
require "action_pack"
require "action_controller"

class TestController < ActionController::Base
end

TestController.view_paths = [File.expand_path("test/benchmarks")]
controller_view = TestController.new.view_context

result = Benchmark.ips do |x|
  x.report("render") do
    controller_view.render("many_partials")
  end
end
[aaron@TC ~/g/r/actionview (speed-up-partials)]$ cat test/benchmarks/test/_many_partials.html.erb
Looping:
<ul>
<% 100.times do |i| %>
  <%= render partial: "list_item", locals: { i: i } %>
<% end %>
</ul>
[aaron@TC ~/g/r/actionview (speed-up-partials)]$ cat test/benchmarks/test/_list_item.html.erb
<li>Number: <%= i %></li>
```

Benchmark results (master):

```
[aaron@TC ~/g/r/actionview (master)]$ be ruby render_benchmark.rb
Warming up --------------------------------------
              render    41.000  i/100ms
Calculating -------------------------------------
              render    424.269  (± 3.5%) i/s -      2.132k in   5.031455s
```

Benchmark results (this branch):

```
[aaron@TC ~/g/r/actionview (speed-up-partials)]$ be ruby render_benchmark.rb
Warming up --------------------------------------
              render    50.000  i/100ms
Calculating -------------------------------------
              render    521.862  (± 3.8%) i/s -      2.650k in   5.085885s
```
  • Loading branch information
tenderlove committed Feb 5, 2019
1 parent 0fa0107 commit 24b068b
Show file tree
Hide file tree
Showing 3 changed files with 41 additions and 19 deletions.
49 changes: 31 additions & 18 deletions actionview/lib/action_view/renderer/partial_renderer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -295,8 +295,21 @@ def initialize(*)
end

def render(context, options, block)
setup(context, options, block)
template = find_partial
as = as_variable(options)
setup(context, options, as, block)

if @path
if @has_object || @collection
@variable, @variable_counter, @variable_iteration = retrieve_variable(@path, as)
@template_keys = retrieve_template_keys(@variable)
else
@template_keys = @locals.keys
end
template = find_partial(@path, @template_keys)
@variable ||= template.variable
else
template = nil
end

@lookup_context.rendered_format ||= begin
if template && template.formats.first
Expand Down Expand Up @@ -359,7 +372,7 @@ def render_partial(view, template)
# If +options[:partial]+ is a string, then the <tt>@path</tt> instance variable is
# set to that string. Otherwise, the +options[:partial]+ object must
# respond to +to_partial_path+ in order to setup the path.
def setup(context, options, block)
def setup(context, options, as, block)
@options = options
@block = block

Expand All @@ -382,25 +395,25 @@ def setup(context, options, block)

if @collection
paths = @collection_data = @collection.map { |o| partial_path(o, context) }
@path = paths.uniq.one? ? paths.first : nil
if paths.uniq.length == 1
@path = paths.first
else
paths.map! { |path| retrieve_variable(path, as).unshift(path) }
@path = nil
end
else
@path = partial_path(@object, context)
end
end

self
end

def as_variable(options)
if as = options[:as]
raise_invalid_option_as(as) unless /\A[a-z_]\w*\z/.match?(as.to_s)
as = as.to_sym
as.to_sym
end

if @path
@variable, @variable_counter, @variable_iteration = retrieve_variable(@path, as)
@template_keys = retrieve_template_keys
else
paths.map! { |path| retrieve_variable(path, as).unshift(path) }
end

self
end

def collection_from_options
Expand All @@ -414,8 +427,8 @@ def collection_from_object
@object.to_ary if @object.respond_to?(:to_ary)
end

def find_partial
find_template(@path, @template_keys) if @path
def find_partial(path, template_keys)
find_template(path, template_keys)
end

def find_template(path, locals)
Expand Down Expand Up @@ -511,9 +524,9 @@ def merge_prefix_into_object_path(prefix, object_path)
end
end

def retrieve_template_keys
def retrieve_template_keys(variable)
keys = @locals.keys
keys << @variable if @has_object || @collection
keys << variable
if @collection
keys << @variable_counter
keys << @variable_iteration
Expand Down
9 changes: 9 additions & 0 deletions actionview/lib/action_view/template.rb
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,8 @@ class Template
end
end

attr_reader :variable

def initialize(source, identifier, handler, details)
format = details[:format] || (handler.default_format if handler.respond_to?(:default_format))

Expand All @@ -138,6 +140,13 @@ def initialize(source, identifier, handler, details)
@original_encoding = nil
@locals = details[:locals] || []
@virtual_path = details[:virtual_path]

@variable = if @virtual_path
base = @virtual_path[-1] == "/" ? "" : File.basename(@virtual_path)
base =~ /\A_?(.*?)(?:\.\w+)*\z/
$1.to_sym
end

@updated_at = details[:updated_at] || Time.now
@formats = Array(format).map { |f| f.respond_to?(:ref) ? f.ref : f }
@variants = [details[:variant]]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
module ActiveRecord
module Railties # :nodoc:
module CollectionCacheAssociationLoading #:nodoc:
def setup(context, options, block)
def setup(context, options, as, block)
@relation = relation_from_options(options)

super
Expand Down

0 comments on commit 24b068b

Please sign in to comment.