From c086017bc961647f98b21d7cdff91c95097505bd Mon Sep 17 00:00:00 2001 From: Christopher Aue Date: Mon, 16 Apr 2018 12:48:12 +0200 Subject: [PATCH] refactored and optimized rendering MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Measures: 1) A while loop is faster than iterating with #each. 2) Check string, variable and block tokens first. They are far more frequent than interrupt tokens. In their case, checking for an interrupt can be avoided. 3) String tokens just map to themselves and don't need the special treatment of BlockBody#render_node (except the resource limit check). Benchmark ========= $ bundle exec rake benchmark:run Before ------ Run 1) parse: 41.630 (± 0.0%) i/s - 420.000 in 10.089309s render: 75.962 (± 3.9%) i/s - 763.000 in 10.066823s parse & render: 25.497 (± 0.0%) i/s - 256.000 in 10.040862s Run 2) parse: 42.130 (± 0.0%) i/s - 424.000 in 10.064738s render: 77.003 (± 1.3%) i/s - 777.000 in 10.093524s parse & render: 25.739 (± 0.0%) i/s - 258.000 in 10.024581s Run 3) parse: 41.976 (± 2.4%) i/s - 420.000 in 10.021406s render: 76.184 (± 1.3%) i/s - 763.000 in 10.018104s parse & render: 25.641 (± 0.0%) i/s - 258.000 in 10.062549s After ----- Run 1) parse: 42.283 (± 0.0%) i/s - 424.000 in 10.028306s render: 83.158 (± 2.4%) i/s - 832.000 in 10.009201s parse & render: 26.417 (± 0.0%) i/s - 266.000 in 10.069718s Run 2) parse: 41.159 (± 4.9%) i/s - 412.000 in 10.031297s render: 81.591 (± 3.7%) i/s - 816.000 in 10.018225s parse & render: 25.924 (± 3.9%) i/s - 260.000 in 10.035653s Run 3) parse: 42.418 (± 2.4%) i/s - 424.000 in 10.003100s render: 84.183 (± 2.4%) i/s - 847.000 in 10.069781s parse & render: 26.726 (± 0.0%) i/s - 268.000 in 10.029857s --- lib/liquid/block_body.rb | 62 +++++++++++++++++++----------------- lib/liquid/profiler/hooks.rb | 8 ++--- 2 files changed, 37 insertions(+), 33 deletions(-) diff --git a/lib/liquid/block_body.rb b/lib/liquid/block_body.rb index 32a7d90..266d8ed 100644 --- a/lib/liquid/block_body.rb +++ b/lib/liquid/block_body.rb @@ -70,33 +70,27 @@ module Liquid output = [] context.resource_limits.render_score += @nodelist.length - @nodelist.each do |token| - # Break out if we have any unhanded interrupts. - break if context.interrupt? - - begin + idx = 0 + while node = @nodelist[idx] + case node + when String + check_resources(context, node) + output << node + when Variable + render_node_to_output(node, output, context) + when Block + render_node_to_output(node, output, context, node.blank?) + break if context.interrupt? # might have happened in a for-block + when Continue, Break # If we get an Interrupt that means the block must stop processing. An # Interrupt is any command that stops block execution such as {% break %} # or {% continue %} - if token.is_a?(Continue) || token.is_a?(Break) - context.push_interrupt(token.interrupt) - break - end - - node_output = render_node(token, context) - - unless token.is_a?(Block) && token.blank? - output << node_output - end - rescue MemoryError => e - raise e - rescue UndefinedVariable, UndefinedDropMethod, UndefinedFilter => e - context.handle_error(e, token.line_number) - output << nil - rescue ::StandardError => e - line_number = token.is_a?(String) ? nil : token.line_number - output << context.handle_error(e, line_number) + context.push_interrupt(node.interrupt) + break + else # Other non-Block tags + render_node_to_output(node, output, context) end + idx += 1 end output.join @@ -104,15 +98,25 @@ module Liquid private - def render_node(node, context) - node_output = node.is_a?(String) ? node : node.render(context) + def render_node_to_output(node, output, context, skip_output = false) + node_output = node.render(context) node_output = node_output.is_a?(Array) ? node_output.join : node_output.to_s + check_resources(context, node_output) + output << node_output unless skip_output + rescue MemoryError => e + raise e + rescue UndefinedVariable, UndefinedDropMethod, UndefinedFilter => e + context.handle_error(e, node.line_number) + output << nil + rescue ::StandardError => e + line_number = node.is_a?(String) ? nil : node.line_number + output << context.handle_error(e, line_number) + end + def check_resources(context, node_output) context.resource_limits.render_length += node_output.length - if context.resource_limits.reached? - raise MemoryError.new("Memory limits exceeded".freeze) - end - node_output + return unless context.resource_limits.reached? + raise MemoryError.new("Memory limits exceeded".freeze) end def create_variable(token, parse_context) diff --git a/lib/liquid/profiler/hooks.rb b/lib/liquid/profiler/hooks.rb index 6f8d798..cb11cd7 100644 --- a/lib/liquid/profiler/hooks.rb +++ b/lib/liquid/profiler/hooks.rb @@ -1,13 +1,13 @@ module Liquid class BlockBody - def render_node_with_profiling(node, context) + def render_node_with_profiling(node, output, context, skip_output = false) Profiler.profile_node_render(node) do - render_node_without_profiling(node, context) + render_node_without_profiling(node, output, context, skip_output) end end - alias_method :render_node_without_profiling, :render_node - alias_method :render_node, :render_node_with_profiling + alias_method :render_node_without_profiling, :render_node_to_output + alias_method :render_node_to_output, :render_node_with_profiling end class Include < Tag