Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions benchmarks.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@ liquid-c:
liquid-compile:
desc: compiles a chosen-for-profiling Liquid theme repeatedly.
category: headline
liquid-il:
desc: renders LiquidIL-generated Ruby for a set of Liquid templates repeatedly (Liquid compiled to standalone Ruby).
category: headline
liquid-render:
desc: renders a chosen-for-profiling Liquid theme repeatedly.
category: headline
Expand Down
13 changes: 13 additions & 0 deletions benchmarks/liquid-il/Gemfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# frozen_string_literal: true

source "https://rubygems.org"
git_source(:github) do |repo_name|
"https://github.com/#{repo_name}.git"
end

# LiquidIL compiles Liquid to Ruby; this benchmark renders its generated Ruby.
# See extract.rb for how generated/ is produced from templates.yml.
gem "liquid-il", github: "tobi/liquid-il", ref: "1b07d162068f1ed2becec1bafd29f36f52dbbd5b"

gem "base64"
gem "bigdecimal"
30 changes: 30 additions & 0 deletions benchmarks/liquid-il/Gemfile.lock
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
GIT
remote: https://github.com/tobi/liquid-il.git
revision: 1b07d162068f1ed2becec1bafd29f36f52dbbd5b
ref: 1b07d162068f1ed2becec1bafd29f36f52dbbd5b
specs:
liquid-il (0.1.0)

GEM
remote: https://rubygems.org/
specs:
base64 (0.3.0)
bigdecimal (4.1.2)

PLATFORMS
ruby
x86_64-linux

DEPENDENCIES
base64
bigdecimal
liquid-il!

CHECKSUMS
base64 (0.3.0) sha256=27337aeabad6ffae05c265c450490628ef3ebd4b67be58257393227588f5a97b
bigdecimal (4.1.2) sha256=53d217666027eab4280346fba98e7d5b66baaae1b9c3c1c0ffe89d48188a3fbd
bundler (4.0.16) sha256=d6ca5dd440c24f9abce9844cf44cc8e18c6a553de65a47efb4544137af92c47d
liquid-il (0.1.0)

BUNDLED WITH
4.0.16
31 changes: 31 additions & 0 deletions benchmarks/liquid-il/benchmark.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
require_relative '../../harness/loader'

Dir.chdir __dir__
use_gemfile

require 'liquid_il'
require 'json'

# LiquidIL compiles Liquid templates to standalone Ruby (see extract.rb). We
# render that committed generated Ruby here, so this benchmark exercises the
# machine-generated code a JIT actually compiles: heavy runtime-helper dispatch,
# partial lambdas, string-buffer building, hash lookups, and loops. The exact
# Ruby lives in generated/ and does not drift with the LiquidIL compiler.
manifest = JSON.parse(File.read(File.join(__dir__, 'manifest.json')))
CASES = manifest.map do |m|
require File.join(__dir__, 'generated', "#{m['spec']}.rb")
mod = Object.const_get(m['module'])
assigns = JSON.parse(File.read(File.join(__dir__, 'fixtures', "#{m['spec']}.json")))
[mod, assigns]
end

# Sanity: every generated module must produce output, or the timing is empty.
CASES.each { |mod, assigns| raise "empty render for #{mod}" if mod.render(assigns).to_s.empty? }

run_benchmark(150) do
# Each render is quick; render the whole template set several times per
# iteration to reduce time-measurement noise (mirrors liquid-render).
1000.times do
CASES.each { |mod, assigns| mod.render(assigns) }
end
end
78 changes: 78 additions & 0 deletions benchmarks/liquid-il/extract.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
# frozen_string_literal: true

# Regenerate the committed LiquidIL-generated Ruby sample from templates.yml.
#
# LiquidIL (https://github.com/tobi/liquid-il) compiles a Liquid template to
# standalone Ruby (Template#to_ruby), inlining static partials so the result
# renders with no file_system. This script compiles each template in
# templates.yml to a module under generated/, writes its render assigns to
# fixtures/, and validates the standalone module reproduces the template's
# expected output. benchmark.rb then renders those committed modules in a hot
# loop, so the exact Ruby the JIT compiles lives in the repo and does not drift
# with the compiler.
#
# BUNDLE_GEMFILE=$PWD/Gemfile bundle exec ruby extract.rb
#
# The generated/ and fixtures/ output is committed; you only need to re-run
# this when templates.yml or the pinned LiquidIL version changes.

require "json"
require "yaml"
require "liquid_il"

HERE = __dir__
GEN_DIR = File.join(HERE, "generated")
FIX_DIR = File.join(HERE, "fixtures")

# Compile-time file system for the templates' inline partials. Static partials
# are inlined into the caller, so the generated Ruby holds no reference to it.
class SpecFileSystem
def initialize(files) = @files = files || {}
def read_template_file(name, _context = nil)
@files.fetch(name.to_s) { raise LiquidIL::FileSystemError, "no such partial: #{name}" }
end
end

def module_name_for(name)
"LiquidILBench" + name.split(/[^a-zA-Z0-9]/).reject(&:empty?).map(&:capitalize).join
end

def deep_dup(obj)
case obj
when Hash then obj.each_with_object({}) { |(k, v), h| h[k] = deep_dup(v) }
when Array then obj.map { |v| deep_dup(v) }
else obj
end
end

Dir.mkdir(GEN_DIR) unless Dir.exist?(GEN_DIR)
Dir.mkdir(FIX_DIR) unless Dir.exist?(FIX_DIR)

specs = YAML.safe_load(File.read(File.join(HERE, "templates.yml")), aliases: true).fetch("specs")
manifest = []

specs.each do |spec|
name = spec.fetch("name")
mod = module_name_for(name)

ctx = LiquidIL::Context.new(file_system: SpecFileSystem.new(spec["filesystem"]))
template = ctx.parse(spec.fetch("template"))
ruby = template.to_ruby(mod)

# Validate the standalone module in an isolated namespace, no file_system.
probe = Module.new
probe.module_eval(ruby, "generated/#{name}.rb")
produced = probe.const_get(mod).render(deep_dup(spec["environment"] || {}))
expected = spec["expected"]
if expected && produced != expected
raise "#{name}: generated Ruby output does not match expected"
end

File.write(File.join(GEN_DIR, "#{name}.rb"), ruby)
File.write(File.join(FIX_DIR, "#{name}.json"), JSON.pretty_generate(spec["environment"] || {}) + "\n")
manifest << { "spec" => name, "module" => mod, "ruby_bytes" => ruby.bytesize }
printf(" ok %-32s %6d B\n", name, ruby.bytesize)
end

File.write(File.join(HERE, "manifest.json"), JSON.pretty_generate(manifest) + "\n")
puts "Extracted #{manifest.size} module(s); #{manifest.sum { |m| m["ruby_bytes"] }} B generated Ruby."
59 changes: 59 additions & 0 deletions benchmarks/liquid-il/fixtures/bench_ecommerce_product_page.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
{
"product": {
"name": "Premium Widget",
"price": 99.99,
"description": "The finest widget money can buy",
"sku": "WDG-001",
"in_stock": true,
"features": [
"Durable",
"Lightweight",
"Eco-friendly"
]
},
"breadcrumbs": [
{
"label": "Home",
"url": "/"
},
{
"label": "Widgets",
"url": "/widgets"
},
{
"label": "Premium Widget",
"url": null
}
],
"reviews": [
{
"author": "Alice",
"rating": 5,
"text": "Amazing!"
},
{
"author": "Bob",
"rating": 4,
"text": "Very good"
},
{
"author": "Carol",
"rating": 5,
"text": "Love it"
}
],
"related": [
{
"name": "Basic Widget",
"price": 29.99
},
{
"name": "Super Widget",
"price": 149.99
},
{
"name": "Widget Pro",
"price": 199.99
}
]
}
112 changes: 112 additions & 0 deletions benchmarks/liquid-il/fixtures/bench_notification_center.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
{
"notifications": [
{
"type": "order",
"order_id": "ORD-001",
"status": "confirmed",
"total": 149.99,
"items": 3,
"date": "2024-01-15"
},
{
"type": "shipping",
"order_id": "ORD-002",
"carrier": "FedEx",
"tracking": "1234567890",
"eta": "Jan 18"
},
{
"type": "review",
"product": "Leather Jacket",
"customer": "Alice",
"rating": 5,
"snippet": "Amazing quality!"
},
{
"type": "promo",
"code": "SAVE20",
"discount": "20%",
"expires": "Jan 31",
"min_purchase": 50
},
{
"type": "stock",
"product": "Blue Widget",
"sku": "BW-001",
"status": "back_in_stock",
"quantity": 25
},
{
"type": "order",
"order_id": "ORD-003",
"status": "processing",
"total": 89.5,
"items": 2,
"date": "2024-01-16"
},
{
"type": "shipping",
"order_id": "ORD-001",
"carrier": "UPS",
"tracking": "9876543210",
"eta": "Jan 17"
},
{
"type": "review",
"product": "Cotton T-Shirt",
"customer": "Bob",
"rating": 4,
"snippet": "Great fit"
},
{
"type": "promo",
"code": "FREESHIP",
"discount": "Free Shipping",
"expires": "Feb 1",
"min_purchase": 75
},
{
"type": "stock",
"product": "Red Scarf",
"sku": "RS-002",
"status": "low_stock",
"quantity": 3
},
{
"type": "order",
"order_id": "ORD-004",
"status": "shipped",
"total": 299.0,
"items": 1,
"date": "2024-01-14"
},
{
"type": "shipping",
"order_id": "ORD-004",
"carrier": "USPS",
"tracking": "5555555555",
"eta": "Jan 19"
},
{
"type": "review",
"product": "Wool Sweater",
"customer": "Carol",
"rating": 5,
"snippet": "Perfect for winter"
},
{
"type": "promo",
"code": "VIP25",
"discount": "25%",
"expires": "Jan 20",
"min_purchase": 100
},
{
"type": "stock",
"product": "Green Hat",
"sku": "GH-003",
"status": "back_in_stock",
"quantity": 50
}
]
}
24 changes: 24 additions & 0 deletions benchmarks/liquid-il/fixtures/bench_render_for_loop.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
{
"items": [
1,
2,
3,
4,
5,
6,
7,
8,
9,
10,
11,
12,
13,
14,
15,
16,
17,
18,
19,
20
]
}
29 changes: 29 additions & 0 deletions benchmarks/liquid-il/fixtures/bench_render_with_params.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
{
"products": [
{
"name": "Widget",
"price": 9.99,
"description": "A useful widget"
},
{
"name": "Gadget",
"price": 19.99,
"description": "An amazing gadget"
},
{
"name": "Gizmo",
"price": 29.99,
"description": "A fantastic gizmo"
},
{
"name": "Doohickey",
"price": 39.99,
"description": "A wonderful doohickey"
},
{
"name": "Thingamajig",
"price": 49.99,
"description": "A marvelous thingamajig"
}
]
}
Loading
Loading