Perf: Remove unneeded to_s and freeze strings

- No longer call to_s on string literals passed to undefined filters
- Freeze all string literals at the top of the generated code

This shows a ~9% performance improvement on the fluid benchmark when run without YJIT
This commit is contained in:
Sam Doiron
2022-04-20 11:03:36 -03:00
parent 7c70b56002
commit ba82f8d05e
2 changed files with 16 additions and 22 deletions
-8
View File
@@ -18,14 +18,6 @@ Liquid::Template.register_filter(ShopFilter)
context = Liquid::Context.new([tables, {}], {}, {}, false, Liquid::ResourceLimits.new(Liquid::Template.default_resource_limits)) context = Liquid::Context.new([tables, {}], {}, {}, false, Liquid::ResourceLimits.new(Liquid::Template.default_resource_limits))
require 'stackprof'
results = StackProf.run(raw: true, out: 'stackprof.dump') do
500_000.times do
@template.render(context)
end
end
Benchmark.ips do |x| Benchmark.ips do |x|
x.report("render") { @template.render(context) } x.report("render") { @template.render(context) }
+16 -14
View File
@@ -31,9 +31,10 @@ module Liquid
show_ruby = ENV["SHOW_RUBY"] && ENV["SHOW_RUBY"].to_i || 0 show_ruby = ENV["SHOW_RUBY"] && ENV["SHOW_RUBY"].to_i || 0
STDERR.puts @ruby if show_ruby >= 1 STDERR.puts @ruby if show_ruby >= 1
res = RubyVM::InstructionSequence.compile(<<~RUBY).eval.call(@nodes) res = RubyVM::InstructionSequence.compile(<<~RUBY).eval.call(@nodes)
# frozen_string_literal: true
->(__nodes) { ->(__nodes) {
->(__context, __output, __l_product) { ->(__context, __output, __l_product) {
__scope = __context.environments.first __assigns = __context.scopes.last
#{@ruby} #{@ruby}
__output __output
} }
@@ -60,10 +61,11 @@ module Liquid
end end
def output(node) def output(node)
self << if node.is_a?(String) compiled = compile_expr(node)
"__output << #{node.inspect}\n" self << if compiled.is_a?(String)
"__output << #{compiled}\n"
else else
"__output << #{compile_expr(node)}.to_s\n" "__output << #{compiled}.to_s\n"
end end
end end
@@ -202,7 +204,7 @@ module Liquid
RUBY RUBY
}, },
"escape" => ->(compiler, expr, args, kwargs) { "escape" => ->(compiler, expr, args, kwargs) {
"(_t = #{expr}; CGI.escape(_t) if _t)" "(_t = #{expr}; CGI.escapeHTML(_t) if _t)"
}, },
"money" => ->(compiler, expr, args, kwargs) { "money" => ->(compiler, expr, args, kwargs) {
"(_m = #{expr}; _m.nil? ? '' : \"$ \#{(_m / 100.0).round(2)}\")" "(_m = #{expr}; _m.nil? ? '' : \"$ \#{(_m / 100.0).round(2)}\")"
@@ -302,26 +304,26 @@ module Liquid
class Condition class Condition
def compile_expr(compiler) def compile_expr(compiler)
condition = if operator if operator
left_expr = compiler.compile_expr(left) left_expr = compiler.compile_expr(left)
right_expr = compiler.compile_expr(right) right_expr = compiler.compile_expr(right)
expr = "((#{left_expr} #{operator} #{right_expr}) #{child_relation}" if child_relation
child_expr = compiler.compile_expr(child_condition)
expr = "((#{left_expr} #{operator} #{right_expr}) #{child_relation} #{child_expr})"
else
expr = "(#{left_expr} #{operator} #{right_expr})"
end
expr
else else
left_expr = compiler.compile_expr(left) left_expr = compiler.compile_expr(left)
right_expr = compiler.compile_expr(right)
expr = "#{left_expr}" expr = "#{left_expr}"
end end
if child_condition
child_expr = compiler.compile_expr(child_condition)
condition << " #{child_expr}"
end
condition
end end
end end
class Assign class Assign
def compile(compiler) def compile(compiler)
compliler.declare(@to) compiler.declare(compiler.var_name(@to))
compiler << "#{compiler.var_name(@to)} = #{compiler.compile_expr(@from)}\n" compiler << "#{compiler.var_name(@to)} = #{compiler.compile_expr(@from)}\n"
end end
end end