From 1662ba6679f6993d3a8b041813f9baac999150a0 Mon Sep 17 00:00:00 2001 From: Florian Weingarten Date: Wed, 6 Jan 2016 19:37:15 +0000 Subject: [PATCH 1/2] Reuse 'forloop' hash to save memory allocations --- lib/liquid/tags/for.rb | 132 ++++++++++++++++++++++------------------- 1 file changed, 72 insertions(+), 60 deletions(-) diff --git a/lib/liquid/tags/for.rb b/lib/liquid/tags/for.rb index ffb924f..e11586b 100644 --- a/lib/liquid/tags/for.rb +++ b/lib/liquid/tags/for.rb @@ -67,69 +67,13 @@ module Liquid end def render(context) - for_offsets = context.registers[:for] ||= Hash.new(0) - for_stack = context.registers[:for_stack] ||= [] + segment = collection_segment(context) - parent_loop = for_stack.last - for_stack.push(nil) - - collection = context.evaluate(@collection_name) - collection = collection.to_a if collection.is_a?(Range) - - from = if @from == :continue - for_offsets[@name].to_i + if segment.empty? + render_else(context) else - context.evaluate(@from).to_i + render_segment(context, segment) end - - limit = context.evaluate(@limit) - to = limit ? limit.to_i + from : nil - - segment = Utils.slice_collection(collection, from, to) - - return render_else(context) if segment.empty? - - segment.reverse! if @reversed - - result = '' - - length = segment.length - - # Store our progress through the collection for the continue flag - for_offsets[@name] = from + segment.length - - context.stack do - segment.each_with_index do |item, index| - context[@variable_name] = item - loop_vars = { - 'name'.freeze => @name, - 'length'.freeze => length, - 'index'.freeze => index + 1, - 'index0'.freeze => index, - 'rindex'.freeze => length - index, - 'rindex0'.freeze => length - index - 1, - 'first'.freeze => (index == 0), - 'last'.freeze => (index == length - 1), - 'parentloop'.freeze => parent_loop - } - - context['forloop'.freeze] = loop_vars - for_stack[-1] = loop_vars - - result << @for_block.render(context) - - # Handle any interrupts if they exist. - if context.interrupt? - interrupt = context.pop_interrupt - break if interrupt.is_a? BreakInterrupt - next if interrupt.is_a? ContinueInterrupt - end - end - end - - result - ensure - for_stack.pop end protected @@ -170,6 +114,74 @@ module Liquid private + def collection_segment(context) + offsets = context.registers[:for] ||= Hash.new(0) + + from = if @from == :continue + offsets[@name].to_i + else + context.evaluate(@from).to_i + end + + collection = context.evaluate(@collection_name) + collection = collection.to_a if collection.is_a?(Range) + + limit = context.evaluate(@limit) + to = limit ? limit.to_i + from : nil + + segment = Utils.slice_collection(collection, from, to) + segment.reverse! if @reversed + + offsets[@name] = from + segment.length + + segment + end + + def render_segment(context, segment) + for_stack = context.registers[:for_stack] ||= [] + length = segment.length + + result = '' + + context.stack do + loop_vars = { + 'name'.freeze => @name, + 'length'.freeze => length, + 'parentloop'.freeze => for_stack[-1] + } + + for_stack.push(loop_vars) + + begin + context['forloop'.freeze] = loop_vars + + segment.each_with_index do |item, index| + context[@variable_name] = item + + loop_vars['index'.freeze] = index + 1 + loop_vars['index0'.freeze] = index + loop_vars['rindex'.freeze] = length - index + loop_vars['rindex0'.freeze] = length - index - 1 + loop_vars['first'.freeze] = (index == 0) + loop_vars['last'.freeze] = (index == length - 1) + + result << @for_block.render(context) + + # Handle any interrupts if they exist. + if context.interrupt? + interrupt = context.pop_interrupt + break if interrupt.is_a? BreakInterrupt + next if interrupt.is_a? ContinueInterrupt + end + end + ensure + for_stack.pop + end + end + + result + end + def set_attribute(key, expr) case key when 'offset'.freeze From e113c891ec081f46fdc4a5605a4135f24c0a6289 Mon Sep 17 00:00:00 2001 From: Florian Weingarten Date: Wed, 6 Jan 2016 21:12:42 +0000 Subject: [PATCH 2/2] Convert forloop hash to drop --- lib/liquid.rb | 1 + lib/liquid/forloop_drop.rb | 42 ++++++++++++++++++++++++++++++++++++++ lib/liquid/tags/for.rb | 15 ++------------ 3 files changed, 45 insertions(+), 13 deletions(-) create mode 100644 lib/liquid/forloop_drop.rb diff --git a/lib/liquid.rb b/lib/liquid.rb index f4c6fea..e8b3538 100644 --- a/lib/liquid.rb +++ b/lib/liquid.rb @@ -48,6 +48,7 @@ require 'liquid/lexer' require 'liquid/parser' require 'liquid/i18n' require 'liquid/drop' +require 'liquid/forloop_drop' require 'liquid/extensions' require 'liquid/errors' require 'liquid/interrupts' diff --git a/lib/liquid/forloop_drop.rb b/lib/liquid/forloop_drop.rb new file mode 100644 index 0000000..81b2d1a --- /dev/null +++ b/lib/liquid/forloop_drop.rb @@ -0,0 +1,42 @@ +module Liquid + class ForloopDrop < Drop + def initialize(name, length, parentloop) + @name = name + @length = length + @parentloop = parentloop + @index = 0 + end + + attr_reader :name, :length, :parentloop + + def index + @index + 1 + end + + def index0 + @index + end + + def rindex + @length - @index + end + + def rindex0 + @length - @index - 1 + end + + def first + @index == 0 + end + + def last + @index == @length - 1 + end + + protected + + def increment! + @index += 1 + end + end +end diff --git a/lib/liquid/tags/for.rb b/lib/liquid/tags/for.rb index e11586b..fa82bc1 100644 --- a/lib/liquid/tags/for.rb +++ b/lib/liquid/tags/for.rb @@ -144,11 +144,7 @@ module Liquid result = '' context.stack do - loop_vars = { - 'name'.freeze => @name, - 'length'.freeze => length, - 'parentloop'.freeze => for_stack[-1] - } + loop_vars = Liquid::ForloopDrop.new(@name, length, for_stack[-1]) for_stack.push(loop_vars) @@ -157,15 +153,8 @@ module Liquid segment.each_with_index do |item, index| context[@variable_name] = item - - loop_vars['index'.freeze] = index + 1 - loop_vars['index0'.freeze] = index - loop_vars['rindex'.freeze] = length - index - loop_vars['rindex0'.freeze] = length - index - 1 - loop_vars['first'.freeze] = (index == 0) - loop_vars['last'.freeze] = (index == length - 1) - result << @for_block.render(context) + loop_vars.send(:increment!) # Handle any interrupts if they exist. if context.interrupt?