From 5a685baee777711acf5cc483ce0016a4fbb4cac4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20L=C3=BCtke?= Date: Mon, 19 May 2025 18:16:58 -0500 Subject: [PATCH] Add benchmark and log results --- OPTIMIZATIONS.md | 31 +++++++++++++++ lib/liquid/block_body.rb | 39 ++++++++++--------- .../unit/block_body_render_benchmark.rb | 18 +++++++++ performance/unit/context_lookup_benchmark.rb | 25 ++++++++++++ 4 files changed, 94 insertions(+), 19 deletions(-) create mode 100644 OPTIMIZATIONS.md create mode 100644 performance/unit/block_body_render_benchmark.rb create mode 100644 performance/unit/context_lookup_benchmark.rb diff --git a/OPTIMIZATIONS.md b/OPTIMIZATIONS.md new file mode 100644 index 000000000..48f61f75a --- /dev/null +++ b/OPTIMIZATIONS.md @@ -0,0 +1,31 @@ +# BlockBody render benchmark + +This benchmark measures the speed of rendering a template with many tags to exercise `BlockBody#render_to_output_buffer`. + +Command: + +``` +bundle exec ruby performance/unit/block_body_render_benchmark.rb +``` + +## Results + +### Before caching `resource_limits` + +``` +ruby 3.4.1 (2024-12-25 revision 48d4efcb85) +YJIT +PRISM [x86_64-linux] +Warming up -------------------------------------- + render many tags 213.000 i/100ms +Calculating ------------------------------------- + render many tags 2.206k (±11.2%) i/s (453.36 μs/i) - 21.939k in 10.105145s +``` + +### After caching `resource_limits` + +``` +ruby 3.4.1 (2024-12-25 revision 48d4efcb85) +YJIT +PRISM [x86_64-linux] +Warming up -------------------------------------- + render many tags 191.000 i/100ms +Calculating ------------------------------------- + render many tags 2.176k (±12.3%) i/s (459.64 μs/i) - 21.392k in 10.030115s +``` diff --git a/lib/liquid/block_body.rb b/lib/liquid/block_body.rb index e4ada7d16..03bc0dc5e 100644 --- a/lib/liquid/block_body.rb +++ b/lib/liquid/block_body.rb @@ -215,30 +215,31 @@ def render(context) render_to_output_buffer(context, +'') end - def render_to_output_buffer(context, output) - freeze unless frozen? - - context.resource_limits.increment_render_score(@nodelist.length) + def render_to_output_buffer(context, output) + freeze unless frozen? + + resource_limits = context.resource_limits + resource_limits.increment_render_score(@nodelist.length) + + idx = 0 + while (node = @nodelist[idx]) + if node.instance_of?(String) + output << node + else + render_node(context, output, node) + # 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 %}. These tags may also occur through Block or Include tags. + break if context.interrupt? # might have happened in a for-block + end + idx += 1 - idx = 0 - while (node = @nodelist[idx]) - if node.instance_of?(String) - output << node - else - render_node(context, output, node) - # 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 %}. These tags may also occur through Block or Include tags. - break if context.interrupt? # might have happened in a for-block + resource_limits.increment_write_score(output) end - idx += 1 - context.resource_limits.increment_write_score(output) + output end - output - end - private def render_node(context, output, node) diff --git a/performance/unit/block_body_render_benchmark.rb b/performance/unit/block_body_render_benchmark.rb new file mode 100644 index 000000000..487afffc9 --- /dev/null +++ b/performance/unit/block_body_render_benchmark.rb @@ -0,0 +1,18 @@ +# frozen_string_literal: true + +require "benchmark/ips" +require 'liquid' + +RubyVM::YJIT.enable if defined?(RubyVM::YJIT) + +source = (['{{a}}'] * 1000).join(' ') +template = Liquid::Template.parse(source) +context = Liquid::Context.new('a' => 'x') + +Benchmark.ips do |x| + x.config(time: 10, warmup: 5) + x.report("render many tags") do + template.render!(context) + end + x.compare! +end diff --git a/performance/unit/context_lookup_benchmark.rb b/performance/unit/context_lookup_benchmark.rb new file mode 100644 index 000000000..d20c8dd8a --- /dev/null +++ b/performance/unit/context_lookup_benchmark.rb @@ -0,0 +1,25 @@ +# frozen_string_literal: true + +require "benchmark/ips" +require 'liquid' + +RubyVM::YJIT.enable if defined?(RubyVM::YJIT) + +context = Liquid::Context.new({ + 'foo' => 'bar', + 'nested' => { 'value' => 42 }, +}) + +Benchmark.ips do |x| + x.config(time: 10, warmup: 5) + + x.report("find_variable top level") do + context.find_variable('foo') + end + + x.report("find_variable nested") do + context.find_variable('nested') + end + + x.compare! +end