Compare commits

...

6 Commits

Author SHA1 Message Date
Lourens Naudé
e853bf5b84 Also dump object distribution 2015-05-06 10:45:20 -04:00
Lourens Naudé
27248f1eb1 Lazy init context errors and interrupts to prevent excess allocation 2015-05-05 23:40:52 -04:00
Lourens Naudé
174839fbef Reuse filters array during variable parsing 2015-05-05 23:30:05 -04:00
Lourens Naudé
01a86728f2 Remove needless intermediate local var in VariableLookup#initialize 2015-05-05 23:21:28 -04:00
Lourens Naudé
0e38f88b58 Recycle the array buffer in BlockBody#render 2015-05-05 23:02:08 -04:00
Lourens Naudé
5a48edae6a Introduce support for memory usage profiles 2015-05-05 23:00:28 -04:00
8 changed files with 57 additions and 17 deletions

View File

@@ -2,6 +2,7 @@ source 'https://rubygems.org'
gemspec gemspec
gem 'stackprof', platforms: :mri_21 gem 'stackprof', platforms: :mri_21
gem 'allocation_tracer', platforms: :mri_21
group :test do group :test do
gem 'spy', '0.4.1' gem 'spy', '0.4.1'

View File

@@ -71,6 +71,20 @@ namespace :profile do
end end
namespace :memory do
desc "Run the liquid memory tracer"
task :run do
ruby "./performance/memory.rb"
end
desc "Run the liquid memory tracer with strict parsing"
task :strict do
ruby "./performance/memory.rb strict"
end
end
desc "Run example" desc "Run example"
task :example do task :example do
ruby "-w -d -Ilib example/server/server.rb" ruby "-w -d -Ilib example/server/server.rb"

View File

@@ -68,7 +68,8 @@ module Liquid
end end
def render(context) def render(context)
output = [] @output ||= []
@output.clear
context.resource_limits.render_score += @nodelist.length context.resource_limits.render_score += @nodelist.length
@nodelist.each do |token| @nodelist.each do |token|
@@ -87,16 +88,16 @@ module Liquid
token_output = render_token(token, context) token_output = render_token(token, context)
unless token.is_a?(Block) && token.blank? unless token.is_a?(Block) && token.blank?
output << token_output @output << token_output
end end
rescue MemoryError => e rescue MemoryError => e
raise e raise e
rescue ::StandardError => e rescue ::StandardError => e
output << context.handle_error(e, token) @output << context.handle_error(e, token)
end end
end end
output.join @output.join
end end
private private

View File

@@ -13,14 +13,13 @@ module Liquid
# #
# context['bob'] #=> nil class Context # context['bob'] #=> nil class Context
class Context class Context
attr_reader :scopes, :errors, :registers, :environments, :resource_limits attr_reader :scopes, :registers, :environments, :resource_limits
attr_accessor :exception_handler attr_accessor :exception_handler
def initialize(environments = {}, outer_scope = {}, registers = {}, rethrow_errors = false, resource_limits = nil) def initialize(environments = {}, outer_scope = {}, registers = {}, rethrow_errors = false, resource_limits = nil)
@environments = [environments].flatten @environments = [environments].flatten
@scopes = [(outer_scope || {})] @scopes = [(outer_scope || {})]
@registers = registers @registers = registers
@errors = []
@resource_limits = resource_limits || ResourceLimits.new(Template.default_resource_limits) @resource_limits = resource_limits || ResourceLimits.new(Template.default_resource_limits)
squash_instance_assigns_with_environments squash_instance_assigns_with_environments
@@ -30,10 +29,14 @@ module Liquid
self.exception_handler = ->(e) { true } self.exception_handler = ->(e) { true }
end end
@interrupts = [] @interrupts = nil
@filters = [] @filters = []
end end
def errors
@errors ||= []
end
def strainer def strainer
@strainer ||= Strainer.create(self, @filters) @strainer ||= Strainer.create(self, @filters)
end end
@@ -50,17 +53,17 @@ module Liquid
# are there any not handled interrupts? # are there any not handled interrupts?
def has_interrupt? def has_interrupt?
!@interrupts.empty? @interrupts && !@interrupts.empty?
end end
# push an interrupt to the stack. this interrupt is considered not handled. # push an interrupt to the stack. this interrupt is considered not handled.
def push_interrupt(e) def push_interrupt(e)
@interrupts.push(e) (@interrupts ||= []).push(e)
end end
# pop an interrupt from the stack # pop an interrupt from the stack
def pop_interrupt def pop_interrupt
@interrupts.pop @interrupts.pop if @interrupts
end end

View File

@@ -33,7 +33,8 @@ module Liquid
end end
def lax_parse(markup) def lax_parse(markup)
@filters = [] @filters ||= []
@filters.clear
if markup =~ /(#{QuotedFragment})(.*)/om if markup =~ /(#{QuotedFragment})(.*)/om
name_markup = $1 name_markup = $1
filter_markup = $2 filter_markup = $2
@@ -52,7 +53,8 @@ module Liquid
end end
def strict_parse(markup) def strict_parse(markup)
@filters = [] @filters ||= []
@filters.clear
p = Parser.new(markup) p = Parser.new(markup)
@name = Expression.parse(p.expression) @name = Expression.parse(p.expression)

View File

@@ -10,21 +10,20 @@ module Liquid
end end
def initialize(markup) def initialize(markup)
lookups = markup.scan(VariableParser) @lookups = markup.scan(VariableParser)
name = lookups.shift name = @lookups.shift
if name =~ SQUARE_BRACKETED if name =~ SQUARE_BRACKETED
name = Expression.parse($1) name = Expression.parse($1)
end end
@name = name @name = name
@lookups = lookups
@command_flags = 0 @command_flags = 0
@lookups.each_index do |i| @lookups.each_index do |i|
lookup = lookups[i] lookup = @lookups[i]
if lookup =~ SQUARE_BRACKETED if lookup =~ SQUARE_BRACKETED
lookups[i] = Expression.parse($1) @lookups[i] = Expression.parse($1)
elsif COMMAND_METHODS.include?(lookup) elsif COMMAND_METHODS.include?(lookup)
@command_flags |= 1 << i @command_flags |= 1 << i
end end

19
performance/memory.rb Normal file
View File

@@ -0,0 +1,19 @@
at_exit do
p 'Objects distribution:'
require 'pp'
pp ObjectSpace.count_objects
end
require 'allocation_tracer' rescue fail("install allocation_tracer extension/gem")
require File.dirname(__FILE__) + '/theme_runner'
Liquid::Template.error_mode = ARGV.first.to_sym if ARGV.first
profiler = ThemeRunner.new
require 'allocation_tracer/trace'
puts "Profiling memory usage..."
200.times do
profiler.run
end

View File

@@ -465,6 +465,7 @@ class ContextUnitTest < Minitest::Test
mock_empty = Spy.on_instance_method(Array, :empty?) mock_empty = Spy.on_instance_method(Array, :empty?)
mock_has_interrupt = Spy.on(@context, :has_interrupt?).and_call_through mock_has_interrupt = Spy.on(@context, :has_interrupt?).and_call_through
@context.push_interrupt(StandardError.new)
@context.has_interrupt? @context.has_interrupt?
refute mock_any.has_been_called? refute mock_any.has_been_called?