From 24b068bea13e8f91397b3440494f0a5326f80882 Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Tue, 5 Feb 2019 15:07:55 -0800 Subject: Speed up partial rendering by caching "variable" calculation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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: [aaron@TC ~/g/r/actionview (speed-up-partials)]$ cat test/benchmarks/test/_list_item.html.erb
  • Number: <%= i %>
  • ``` 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 ``` --- .../lib/action_view/renderer/partial_renderer.rb | 49 ++++++++++++++-------- actionview/lib/action_view/template.rb | 9 ++++ 2 files changed, 40 insertions(+), 18 deletions(-) (limited to 'actionview') diff --git a/actionview/lib/action_view/renderer/partial_renderer.rb b/actionview/lib/action_view/renderer/partial_renderer.rb index 478400e016..801916954f 100644 --- a/actionview/lib/action_view/renderer/partial_renderer.rb +++ b/actionview/lib/action_view/renderer/partial_renderer.rb @@ -295,8 +295,21 @@ module ActionView 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 @@ -359,7 +372,7 @@ module ActionView # If +options[:partial]+ is a string, then the @path 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 @@ -382,25 +395,25 @@ module ActionView 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 @@ -414,8 +427,8 @@ module ActionView @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) @@ -511,9 +524,9 @@ module ActionView 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 diff --git a/actionview/lib/action_view/template.rb b/actionview/lib/action_view/template.rb index 3b2c264ed4..8a5407c622 100644 --- a/actionview/lib/action_view/template.rb +++ b/actionview/lib/action_view/template.rb @@ -128,6 +128,8 @@ module ActionView end end + attr_reader :variable + def initialize(source, identifier, handler, details) format = details[:format] || (handler.default_format if handler.respond_to?(:default_format)) @@ -138,6 +140,13 @@ module ActionView @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]] -- cgit v1.2.3