Compare commits

...
6 Commits
Author SHA1 Message Date
Sam Doiron ba82f8d05e 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
2022-04-20 11:03:36 -03:00
Sam Doiron 7c70b56002 Fix implementation of escape and money filters 2022-04-18 11:22:19 -03:00
Sam Doiron 8a71d29bfb Add + tailor fluid benchmark 2022-04-18 10:32:44 -03:00
Sam Doiron f6d1b56b4e Add single-benchmark execute, simplify compilation 2022-04-13 11:14:23 -03:00
Sam Doiron 45ddc00710 Add benchmark viewer 2022-04-07 18:15:18 -03:00
Sam Doiron c2b7b3df50 Almost all tests passing, except profiler 2022-04-06 17:35:00 -03:00
22 changed files with 1909 additions and 23 deletions
+5
View File
@@ -11,12 +11,17 @@ group :benchmark, :test do
gem 'benchmark-ips'
gem 'memory_profiler'
gem 'terminal-table'
gem 'unicode_plot'
install_if -> { RUBY_PLATFORM !~ /mingw|mswin|java/ && RUBY_ENGINE != 'truffleruby' } do
gem 'stackprof'
end
end
group :development do
gem 'pry-byebug'
end
group :test do
gem 'rubocop', '~> 1.4', require: false
gem 'rubocop-shopify', '~> 1.0.7', require: false
+31
View File
@@ -53,6 +53,21 @@ task :test do
Rake::Task['integration_test'].reenable
Rake::Task['integration_test'].invoke
end
if RUBY_ENGINE == 'ruby'
Rake::Task['test_compile'].invoke
end
end
task :test_compile do
ENV['LIQUID_COMPILE'] = '1'
ENV['LIQUID_PARSER_MODE'] = 'lax'
Rake::Task['integration_test'].reenable
Rake::Task['integration_test'].invoke
ENV['LIQUID_PARSER_MODE'] = 'strict'
Rake::Task['integration_test'].reenable
Rake::Task['integration_test'].invoke
end
task(gem: :build)
@@ -77,6 +92,22 @@ namespace :benchmark do
ruby "./performance/benchmark.rb lax"
end
desc "Compare the render performance of compiled Liquid to standard Liquid and Liquid-C"
task :compare_render do
ENV.delete("LIQUID_C")
ENV.delete("RENDER_ONLY")
ENV["LIQUID_COMPILE"] = "1"
ruby "./performance/benchmark.rb strict"
ENV.delete("LIQUID_COMPILE")
ruby "./performance/benchmark.rb strict"
ENV["LIQUID_C"] = "1"
ruby "./performance/benchmark.rb strict"
end
desc "Run the liquid benchmark with strict parsing"
task :strict do
ruby "./performance/benchmark.rb strict"
+26
View File
@@ -0,0 +1,26 @@
# https://github.com/evanphx/benchmark-ips
require 'liquid'
#require 'liquid/c'
require 'benchmark/ips'
require_relative '../lib/liquid/compile'
require_relative 'shop_filter'
require_relative 'money_filter'
# Each database table is a hash
require_relative 'database'
tables = Database.tables
Liquid::Template.register_filter(MoneyFilter)
Liquid::Template.register_filter(ShopFilter)
@template = Liquid::Template.parse(File.read("product.liquid"))
context = Liquid::Context.new([tables, {}], {}, {}, false, Liquid::ResourceLimits.new(Liquid::Template.default_resource_limits))
Benchmark.ips do |x|
x.report("render") { @template.render(context) }
# Compare the iterations per second of the various reports
x.compare!
end
+57
View File
@@ -0,0 +1,57 @@
# frozen_string_literal: true
require 'yaml'
require 'stackprof'
module Database
DATABASE_FILE_PATH = "#{__dir__}/vision.database.yml"
# Load the standard vision toolkit database and re-arrage it to be simply exportable
# to liquid as assigns. All this is based on Shopify
def self.tables
@tables ||= begin
db =
if YAML.respond_to?(:unsafe_load_file) # Only Psych 4+ can use unsafe_load_file
# unsafe_load_file is needed for YAML references
YAML.unsafe_load_file(DATABASE_FILE_PATH, symbolize_names: true)
else
YAML.load_file(DATABASE_FILE_PATH, symbolize_names: true)
end
# From vision source
db[:products].each do |product|
collections = db[:collections].find_all do |collection|
collection[:products].any? { |p| p[:id].to_i == product[:id].to_i }
end
product[:collections] = collections
end
# key the tables by handles, as this is how liquid expects it.
db = db.each_with_object({}) do |(key, values), assigns|
assigns[key] = values.each_with_object({}) do |v, h|
h[v[:handle]] = v
end
end
# Some standard direct accessors so that the specialized templates
# render correctly
db[:collection] = db[:collections].values.first
db[:product] = db[:products].values.first
db[:blog] = db[:blogs].values.first
db[:article] = db[:blog][:articles].first
db[:cart] = {
:total_price => db[:line_items].values.inject(0) { |sum, item| sum + item[:line_price] * item[:quantity] },
:item_count => db[:line_items].values.inject(0) { |sum, item| sum + item[:quantity] },
:items => db[:line_items].values,
}
db
end
end
end
if __FILE__ == $PROGRAM_NAME
p(Database.tables[:collections][:frontpage].keys)
# p Database.tables[:blog][:articles]
end
+19
View File
@@ -0,0 +1,19 @@
# frozen_string_literal: true
module MoneyFilter
def money_with_currency(money)
return '' if money.nil?
format("$ %.2f USD", money / 100.0)
end
def money(money)
return '' if money.nil?
format("$ %.2f", money / 100.0)
end
private
def currency
ShopDrop.new.currency
end
end
+36
View File
@@ -0,0 +1,36 @@
<div id="product-left">
{% for image in product.images %}{% if forloop.first %}<div id="product-image">
<a href="{{ image | product_img_url: 'large' }}" rel="lightbox[images]" title="{{ product.title | escape }}"><img src="{{ image | product_img_url: 'medium' }}" alt="{{ product.title | escape }}" /></a>
</div>{% else %}
<div class="product-images">
<a href="{{ image | product_img_url: 'large' }}" rel="lightbox[images]" title="{{ product.title | escape }}"><img src="{{ image | product_img_url: 'small' }}" alt="{{ product.title | escape }}" /></a>
</div>{% endif %}{% endfor %}
</div>
<div id="product-right">
<h1>{{ product.title }}</h1>
{{ product.description }}
{% if product.available %}
<form action="/cart/add" method="post">
<div id="product-variants">
<div id="price-field"></div>
<select id="product-select" name='id'>
{% for variant in product.variants %}
<option value="{{ variant.id }}">{{ variant.title }} - {{ variant.price | money }}</option>
{% endfor %}
</select>
</div>
<input type="image" src="{{ 'purchase.png' | asset_url }}" name="add" value="Purchase" id="purchase" />
</form>
{% else %}
<p class="bold-red">This product is temporarily unavailable</p>
{% endif %}
<div id="product-details">
<strong>Continue Shopping</strong><br />
Browse more {{ product.type | link_to_type }} or additional {{ product.vendor | link_to_vendor }} products.
</div>
</div>
+106
View File
@@ -0,0 +1,106 @@
# frozen_string_literal: true
module ShopFilter
def asset_url(input)
"/files/1/[shop_id]/[shop_id]/assets/#{input}"
end
def global_asset_url(input)
"/global/#{input}"
end
def shopify_asset_url(input)
"/shopify/#{input}"
end
def script_tag(url)
%(<script src="#{url}" type="text/javascript"></script>)
end
def stylesheet_tag(url, media = "all")
%(<link href="#{url}" rel="stylesheet" type="text/css" media="#{media}" />)
end
def link_to(link, url, title = "")
%(<a href="#{url}" title="#{title}">#{link}</a>)
end
def img_tag(url, alt = "")
%(<img src="#{url}" alt="#{alt}" />)
end
def link_to_vendor(vendor)
if vendor
link_to(vendor, url_for_vendor(vendor), vendor)
else
'Unknown Vendor'
end
end
def link_to_type(type)
if type
link_to(type, url_for_type(type), type)
else
'Unknown Vendor'
end
end
def url_for_vendor(vendor_title)
"/collections/#{to_handle(vendor_title)}"
end
def url_for_type(type_title)
"/collections/#{to_handle(type_title)}"
end
def product_img_url(url, style = 'small')
unless url =~ %r{\Aproducts/([\w\-\_]+)\.(\w{2,4})}
raise ArgumentError, 'filter "size" can only be called on product images'
end
case style
when 'original'
'/files/shops/random_number/' + url
when 'grande', 'large', 'medium', 'compact', 'small', 'thumb', 'icon'
"/files/shops/random_number/products/#{Regexp.last_match(1)}_#{style}.#{Regexp.last_match(2)}"
else
raise ArgumentError, 'valid parameters for filter "size" are: original, grande, large, medium, compact, small, thumb and icon '
end
end
def default_pagination(paginate)
html = []
html << %(<span class="prev">#{link_to(paginate['previous']['title'], paginate['previous']['url'])}</span>) if paginate['previous']
paginate['parts'].each do |part|
html << if part['is_link']
%(<span class="page">#{link_to(part['title'], part['url'])}</span>)
elsif part['title'].to_i == paginate['current_page'].to_i
%(<span class="page current">#{part['title']}</span>)
else
%(<span class="deco">#{part['title']}</span>)
end
end
html << %(<span class="next">#{link_to(paginate['next']['title'], paginate['next']['url'])}</span>) if paginate['next']
html.join(' ')
end
# Accepts a number, and two words - one for singular, one for plural
# Returns the singular word if input equals 1, otherwise plural
def pluralize(input, singular, plural)
input == 1 ? singular : plural
end
private
def to_handle(str)
result = str.dup
result.downcase!
result.delete!("'\"()[]")
result.gsub!(/\W+/, '-')
result.gsub!(/-+\z/, '') if result[-1] == '-'
result.gsub!(/\A-+/, '') if result[0] == '-'
result
end
end
Binary file not shown.
+945
View File
@@ -0,0 +1,945 @@
# =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
# Variants
# =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
product_variants:
- &product-1-var-1
id: 1
title: 151cm / Normal
price: 19900
weight: 1000
compare_at_price: 49900
available: true
inventory_quantity: 5
option1: 151cm
option2: Normal
option3:
- &product-1-var-2
id: 2
title: 155cm / Normal
price: 31900
weight: 1000
compare_at_price: 50900
available: true
inventory_quantity: 2
option1: 155cm
option2: Normal
option3:
- &product-2-var-1
id: 3
title: 162cm
price: 29900
weight: 1000
compare_at_price: 52900
available: true
inventory_quantity: 3
option1: 162cm
option2:
option3:
- &product-3-var-1
id: 4
title: 159cm
price: 19900
weight: 1000
compare_at_price:
available: true
inventory_quantity: 4
option1: 159cm
option2:
option3:
- &product-4-var-1
id: 5
title: 159cm
price: 19900
weight: 1000
compare_at_price: 32900
available: true
inventory_quantity: 6
option1: 159cm
option2:
option3:
- &product-1-var-3
id: 6
title: 158cm / Wide
price: 23900
weight: 1000
compare_at_price: 99900
available: false
inventory_quantity: 0
option1: 158cm
option2: Wide
option3:
- &product-3-var-2
id: 7
title: 162cm
price: 19900
weight: 1000
compare_at_price:
available: false
inventory_quantity: 0
option1: 162cm
option2:
option3:
- &product-3-var-3
id: 8
title: 165cm
price: 22900
weight: 1000
compare_at_price:
available: true
inventory_quantity: 4
option1: 165cm
option2:
option3:
- &product-5-var-1
id: 9
title: black / 42
price: 11900
weight: 500
compare_at_price: 22900
available: true
inventory_quantity: 1
option1: black
option2: 42
option3:
- &product-5-var-2
id: 10
title: beige / 42
price: 11900
weight: 500
compare_at_price: 22900
available: true
inventory_quantity: 3
option1: beige
option2: 42
option3:
- &product-5-var-3
id: 11
title: white / 42
price: 13900
weight: 500
compare_at_price: 24900
available: true
inventory_quantity: 1
option1: white
option2: 42
option3:
- &product-5-var-4
id: 12
title: black / 44
price: 11900
weight: 500
compare_at_price: 22900
available: true
inventory_quantity: 2
option1: black
option2: 44
option3:
- &product-5-var-5
id: 13
title: beige / 44
price: 11900
weight: 500
compare_at_price: 22900
available: false
inventory_quantity: 0
option1: beige
option2: 44
option3:
- &product-5-var-6
id: 14
title: white / 44
price: 13900
weight: 500
compare_at_price: 24900
available: false
inventory_quantity: 0
option1: white
option2: 44
option3:
- &product-6-var-1
id: 15
title: red
price: 2179500
weight: 200000
compare_at_price:
available: true
inventory_quantity: 0
option1: red
option2:
option3:
- &product-7-var-1
id: 16
title: black / small
price: 1900
weight: 200
compare_at_price:
available: true
inventory_quantity: 20
option1: black
option2: small
option3:
- &product-7-var-2
id: 17
title: black / medium
price: 1900
weight: 200
compare_at_price:
available: false
inventory_quantity: 0
option1: black
option2: medium
option3:
- &product-7-var-3
id: 18
title: black / large
price: 1900
weight: 200
compare_at_price:
available: true
inventory_quantity: 10
option1: black
option2: large
option3:
- &product-7-var-4
id: 19
title: black / extra large
price: 1900
weight: 200
compare_at_price:
available: false
inventory_quantity: 0
option1: black
option2: extra large
option3:
- &product-8-var-1
id: 20
title: brown / small
price: 5900
weight: 400
compare_at_price: 6900
available: true
inventory_quantity: 5
option1: brown
option2: small
option3:
- &product-8-var-2
id: 21
title: brown / medium
price: 5900
weight: 400
compare_at_price: 6900
available: false
inventory_quantity: 0
option1: brown
option2: medium
option3:
- &product-8-var-3
id: 22
title: brown / large
price: 5900
weight: 400
compare_at_price: 6900
available: true
inventory_quantity: 10
option1: brown
option2: large
option3:
- &product-8-var-4
id: 23
title: black / small
price: 5900
weight: 400
compare_at_price: 6900
available: true
inventory_quantity: 10
option1: black
option2: small
option3:
- &product-8-var-5
id: 24
title: black / medium
price: 5900
weight: 400
compare_at_price: 6900
available: true
inventory_quantity: 10
option1: black
option2: medium
option3:
- &product-8-var-6
id: 25
title: black / large
price: 5900
weight: 400
compare_at_price: 6900
available: false
inventory_quantity: 0
option1: black
option2: large
option3:
- &product-9-var-1
id: 26
title: Body Only
price: 499995
weight: 2000
compare_at_price:
available: true
inventory_quantity: 3
option1: Body Only
option2:
option3:
- &product-9-var-2
id: 27
title: Kit with 18-55mm VR lens
price: 523995
weight: 2000
compare_at_price:
available: true
inventory_quantity: 2
option1: Kit with 18-55mm VR lens
option2:
option3:
- &product-9-var-3
id: 28
title: Kit with 18-200 VR lens
price: 552500
weight: 2000
compare_at_price:
available: true
inventory_quantity: 3
option1: Kit with 18-200 VR lens
option2:
option3:
# =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
# Products
# =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
products:
- &product-1
id: 1
title: Arbor Draft
handle: arbor-draft
type: Snowboards
vendor: Arbor
price: 23900
price_max: 31900
price_min: 23900
price_varies: true
available: true
tags:
- season2005
- pro
- intermediate
- wooden
- freestyle
options:
- Length
- Style
compare_at_price: 49900
compare_at_price_max: 50900
compare_at_price_min: 49900
compare_at_price_varies: true
url: /products/arbor-draft
featured_image: products/arbor_draft.jpg
images:
- products/arbor_draft.jpg
description:
The Arbor Draft snowboard wouldn't exist if Polynesians hadn't figured out how to surf hundreds of years ago. But the Draft does exist, and it's here to bring your urban and park riding to a new level. The board's freaky Tiki design pays homage to culture that inspired snowboarding. It's designed to spin with ease, land smoothly, lock hook-free onto rails, and take the abuse of a pavement pounding or twelve. The Draft will pop off kickers with authority and carve solidly across the pipe. The Draft features targeted Koa wood die cuts inlayed into the deck that enhance the flex pattern. Now bow down to riding's ancestors.
variants:
- *product-1-var-1
- *product-1-var-2
- *product-1-var-3
- &product-2
id: 2
title: Arbor Element
handle: arbor-element
type: Snowboards
vendor: Arbor
price: 29900
price_max: 29900
price_min: 29900
price_varies: false
available: true
tags:
- season2005
- pro
- wooden
- freestyle
options:
- Length
compare_at_price: 52900
compare_at_price_max: 52900
compare_at_price_min: 52900
compare_at_price_varies: false
url: /products/arbor-element
featured_image: products/element58.jpg
images:
- products/element58.jpg
description:
The Element is a technically advanced all-mountain board for riders who readily transition from one terrain, snow condition, or riding style to another. Its balanced design provides the versatility needed for the true ride-it-all experience. The Element is exceedingly lively, freely initiates, and holds a tight edge at speed. Its structural real-wood topsheet is made with book-matched Koa.
variants:
- *product-2-var-1
- &product-3
id: 3
title: Comic ~ Pastel
handle: comic-pastel
type: Snowboards
vendor: Technine
price: 19900
price_max: 22900
price_min: 19900
tags:
- season2006
- beginner
- intermediate
- freestyle
- purple
options:
- Length
price_varies: true
available: true
compare_at_price:
compare_at_price_max: 0
compare_at_price_min: 0
compare_at_price_varies: false
url: /products/comic-pastel
featured_image: products/technine1.jpg
images:
- products/technine1.jpg
- products/technine2.jpg
- products/technine_detail.jpg
description:
2005 Technine Comic Series Description The Comic series was developed to be the ultimate progressive freestyle board in the Technine line. Dependable edge control and a perfect flex pattern for jumping in the park or out of bounds. Landins and progression will come easy with this board and it will help your riding progress to the next level. Street rails, park jibs, backcountry booters and park jumps, this board will do it all.
variants:
- *product-3-var-1
- *product-3-var-2
- *product-3-var-3
- &product-4
id: 4
title: Comic ~ Orange
handle: comic-orange
type: Snowboards
vendor: Technine
price: 19900
price_max: 19900
price_min: 19900
price_varies: false
available: true
tags:
- season2006
- beginner
- intermediate
- freestyle
- orange
options:
- Length
compare_at_price: 32900
compare_at_price_max: 32900
compare_at_price_min: 32900
compare_at_price_varies: false
url: /products/comic-orange
featured_image: products/technine3.jpg
images:
- products/technine3.jpg
- products/technine4.jpg
description:
2005 Technine Comic Series Description The Comic series was developed to be the ultimate progressive freestyle board in the Technine line. Dependable edge control and a perfect flex pattern for jumping in the park or out of bounds. Landins and progression will come easy with this board and it will help your riding progress to the next level. Street rails, park jibs, backcountry booters and park jumps, this board will do it all.
variants:
- *product-4-var-1
- &product-5
id: 5
title: Burton Boots
handle: burton-boots
type: Boots
vendor: Burton
price: 11900
price_max: 11900
price_min: 11900
price_varies: false
available: true
tags:
- season2006
- beginner
- intermediate
- boots
options:
- Color
- Shoe Size
compare_at_price: 22900
compare_at_price_max: 22900
compare_at_price_min: 22900
compare_at_price_varies: false
url: /products/burton-boots
featured_image: products/burton.jpg
images:
- products/burton.jpg
description:
The Burton boots are particularly well on snowboards. The very best thing about them is that the according picture is cubic. This makes testing in a Vision testing environment very easy.
variants:
- *product-5-var-1
- *product-5-var-2
- *product-5-var-3
- *product-5-var-4
- *product-5-var-5
- *product-5-var-6
- &product-6
id: 6
title: Superbike 1198 S
handle: superbike
type: Superbike
vendor: Ducati
price: 2179500
price_max: 2179500
price_min: 2179500
price_varies: false
available: true
tags:
- ducati
- superbike
- bike
- street
- racing
- performance
options:
- Color
compare_at_price:
compare_at_price_max: 0
compare_at_price_min: 0
compare_at_price_varies: false
url: /products/superbike
featured_image: products/ducati.jpg
images:
- products/ducati.jpg
description:
<h3>S PERFORMANCE</h3>
<p>Producing 170hp (125kW) and with a dry weight of just 169kg (372.6lb), the new 1198 S now incorporates more World Superbike technology than ever before by taking the 1198 motor and adding top-of-the-range suspension, lightweight chassis components and a true racing-style traction control system designed for road use.</p>
<p>The high performance, fully adjustable 43mm Öhlins forks, which sport low friction titanium nitride-treated fork sliders, respond effortlessly to every imperfection in the tarmac. Beyond their advanced engineering solutions, one of the most important characteristics of Öhlins forks is their ability to communicate the condition and quality of the tyre-to-road contact patch, a feature that puts every rider in superior control. The suspension set-up at the rear is complemented with a fully adjustable Öhlins rear shock equipped with a ride enhancing top-out spring and mounted to a single-sided swingarm for outstanding drive and traction. The front-to-rear Öhlins package is completed with a control-enhancing adjustable steering damper.</p>
variants:
- *product-6-var-1
- &product-7
id: 7
title: Shopify Shirt
handle: shopify-shirt
type: Shirt
vendor: Shopify
price: 1900
price_max: 1900
price_min: 1900
price_varies: false
available: true
tags:
- shopify
- shirt
- apparel
- tshirt
- clothing
options:
- Color
- Size
compare_at_price:
compare_at_price_max: 0
compare_at_price_min: 0
compare_at_price_varies: false
url: /products/shopify-shirt
featured_image: products/shopify_shirt.png
images:
- products/shopify_shirt.png
description:
<p>High Quality Shopify Shirt. Wear your e-commerce solution with pride and attract attention anywhere you go.</p>
<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
variants:
- *product-7-var-1
- *product-7-var-2
- *product-7-var-3
- *product-7-var-4
- &product-8
id: 8
title: Hooded Sweater
handle: hooded-sweater
type: Sweater
vendor: Stormtech
price: 5900
price_max: 5900
price_min: 5900
price_varies: false
available: true
tags:
- sweater
- hooded
- apparel
- clothing
options:
- Color
- Size
compare_at_price: 6900
compare_at_price_max: 6900
compare_at_price_min: 6900
compare_at_price_varies: false
url: /products/hooded-sweater
featured_image: products/hooded-sweater.jpg
images:
- products/hooded-sweater.jpg
- products/hooded-sweater-b.jpg
description:
<p>Extra comfortable zip up sweater. Durable quality, ideal for any outdoor activities.</p>
<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
variants:
- *product-8-var-1
- *product-8-var-2
- *product-8-var-3
- *product-8-var-4
- *product-8-var-5
- *product-8-var-6
- &product-9
id: 9
title: D3 Digital SLR Camera
handle: d3
type: SLR
vendor: Nikon
price: 499995
price_max: 552500
price_min: 499995
price_varies: true
available: true
tags:
- camera
- slr
- nikon
- professional
options:
- Bundle
compare_at_price:
compare_at_price_max: 0
compare_at_price_min: 0
compare_at_price_varies: false
url: /products/d3
featured_image: products/d3.jpg
images:
- products/d3.jpg
- products/d3_2.jpg
- products/d3_3.jpg
description:
<p>Flagship pro D-SLR with a 12.1-MP FX-format CMOS sensor, blazing 9 fps shooting at full FX resolution and low-noise performance up to 6400 ISO.</p>
<p><strong>Nikon's original 12.1-megapixel FX-format (23.9 x 36mm) CMOS sensor:</strong> Couple Nikon's exclusive digital image processing system with the 12.1-megapixel FX-format and you'll get breathtakingly rich images while also reducing noise to unprecedented levels with even higher ISOs.</p>
<p><strong>Continuous shooting at up to 9 frames per second:</strong> At full FX resolution and up to 11fps in the DX crop mode, the D3 offers uncompromised shooting speeds for fast-action and sports photography.</p>
variants:
- *product-9-var-1
- *product-9-var-2
- *product-9-var-3
# =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
# Line Items
# =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
line_items:
- &line_item-1
id: 1
title: 'Arbor Draft'
subtitle: '151cm'
price: 29900
line_price: 29900
quantity: 1
variant: *product-1-var-1
product: *product-1
- &line_item-2
id: 2
title: 'Comic ~ Orange'
subtitle: '159cm'
price: 19900
line_price: 39800
quantity: 2
variant: *product-4-var-1
product: *product-4
# =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
# Link Lists
# =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
links:
- &link-1
id: 1
title: Our Sale
url: /collections/sale
- &link-2
id: 2
title: Arbor Stuff
url: /collections/arbor
- &link-3
id: 3
title: All our Snowboards
url: /collections/snowboards
- &link-4
id: 4
title: Powered by Shopify
url: 'http://shopify.com'
- &link-5
id: 5
title: About Us
url: /pages/about-us
- &link-6
id: 6
title: Policies
url: /pages/shipping
- &link-7
id: 7
title: Contact Us
url: /pages/contact
- &link-8
id: 8
title: Our blog
url: /blogs/bigcheese-blog
- &link-9
id: 9
title: New Boots
url: /products/burton-boots
- &link-10
id: 10
title: Paginated Sale
url: /collections/paginated-sale
- &link-11
id: 11
title: Our Paginated blog
url: /blogs/paginated-blog
- &link-12
id: 12
title: Catalog
url: /collections/all
# =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
# Link Lists
# =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
link_lists:
- &link-list-1
id: 1
title: 'Main Menu'
handle: 'main-menu'
links:
- *link-12
- *link-5
- *link-7
- *link-8
- &link-list-2
id: 1
title: 'Footer Menu'
handle: 'footer'
links:
- *link-5
- *link-6
# =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
# Collections
# =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
collections:
- &collection-1
id: 1
title: Frontpage
handle: frontpage
url: /collections/frontpage
products:
- *product-7
- *product-8
- *product-9
- &collection-2
id: 2
title: Arbor
handle: arbor
url: /collections/arbor
products:
- *product-1
- *product-2
- &collection-3
id: 3
title: Snowboards
handle: snowboards
url: /collections/snowboards
description:
<p>This is a description for my <strong>Snowboards</strong> collection.</p>
products:
- *product-1
- *product-2
- *product-3
- *product-4
- &collection-4
id: 4
title: Items On Sale
handle: sale
url: /collections/sale
products:
- *product-1
- &collection-5
id: 5
title: Paginated Sale
handle: 'paginated-sale'
url: '/collections/paginated-sale'
products:
- *product-1
- *product-2
- *product-3
- *product-4
products_count: 210
- &collection-6
id: 6
title: All products
handle: 'all'
url: '/collections/all'
products:
- *product-7
- *product-8
- *product-9
- *product-6
- *product-1
- *product-2
- *product-3
- *product-4
- *product-5
# =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
# Pages and Blogs
# =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
pages:
- &page-2
id: 1
title: Contact Us
handle: contact
url: /pages/contact
author: Tobi
content:
"<p>You can contact us via phone under (555) 567-2222.</p>
<p>Our retail store is located at <em>Rue d'Avignon 32, Avignon (Provence)</em>.</p>
<p><strong>Opening Hours:</strong><br />Monday through Friday: 9am - 6pm<br />Saturday: 10am - 3pm<br />Sunday: closed</p>"
created_at: 2005-04-04 12:00
- &page-3
id: 2
title: About Us
handle: about-us
url: /pages/about-us
author: Tobi
content:
"<p>Our company was founded in 1894 and we are since operating out of Avignon from the beautiful Provence.</p>
<p>We offer the highest quality products and are proud to serve our customers to their heart's content.</p>"
created_at: 2005-04-04 12:00
- &page-4
id: 3
title: Shopping Cart
handle: shopping-cart
url: /pages/shopping-cart
author: Tobi
content: "<ul><li>Your order is safe with us. Our checkout uses industry standard security to protect your information.</li><li>Your order will be billed immediately upon checkout.</li><li><b>ALL SALES ARE FINAL:</b> Defective or damaged product will be exchanged</li><li>All orders are processed expediently: usually in under 24 hours.</li></ul>"
created_at: 2005-04-04 12:00
- &page-5
id: 4
title: Shipping and Handling
handle: shipping
url: /pages/shipping
author: Tobi
content: <p>Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
created_at: 2005-04-04 12:00
- &page-6
id: 5
title: Frontpage
handle: frontpage
url: /pages/frontpage
author: Tobi
content: <p>Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
created_at: 2005-04-04 12:00
blogs:
- id: 1
handle: news
title: News
url: /blogs/news
articles:
- id: 3
title: 'Welcome to the new Foo Shop'
author: Daniel
content: <p><strong>Welcome to your Shopify store! The jaded Pixel crew is really glad you decided to take Shopify for a spin.</strong></p><p>To help you get you started with Shopify, here are a couple of tips regarding what you see on this page.</p><p>The text you see here is an article. To edit this article, create new articles or create new pages you can go to the <a href="/admin/pages">Blogs &amp; Pages</a> tab of the administration menu.</p><p>The Shopify t-shirt above is a product and selling products is what Shopify is all about. To edit this product, or create new products you can go to the <a href="/admin/products">Products Tab</a> in of the administration menu.</p><p>While you're looking around be sure to check out the <a href="/admin/collections">Collections</a> and <a href="/admin/links">Navigations</a> tabs and soon you will be well on your way to populating your site.</p><p>And of course don't forget to browse the <a href="admin/design/appearance/themes">theme gallery</a> to pick a new look for your shop!</p><p><strong>Shopify is in beta</strong><br />If you would like to make comments or suggestions please visit us in the <a href="http://forums.shopify.com/community">Shopify Forums</a> or drop us an <a href="mailto:[email protected]">email</a>.</p>
created_at: 2005-04-04 16:00
- id: 4
title: 'Breaking News: Restock on all sales products'
author: Tobi
content: Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
created_at: 2005-04-04 12:00
articles_count: 2
- id: 2
handle: bigcheese-blog
title: Bigcheese blog
url: /blogs/bigcheese-blog
articles:
- id: 1
title: 'One thing you probably did not know yet...'
author: Justin
content: Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
created_at: 2005-04-04 16:00
comments:
-
id: 1
author: John Smith
email: [email protected]
content: Wow...great article man.
status: published
created_at: 2009-01-01 12:00
updated_at: 2009-02-01 12:00
url: ""
-
id: 2
author: John Jones
email: [email protected]
content: I really enjoyed this article. And I love your shop! It's awesome. Shopify rocks!
status: published
created_at: 2009-03-01 12:00
updated_at: 2009-02-01 12:00
url: "http://somesite.com/"
- id: 2
title: Fascinating
author: Tobi
content: Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
created_at: 2005-04-06 12:00
comments:
articles_count: 2
comments_enabled?: true
comment_post_url: ""
comments_count: 2
moderated?: true
- id: 3
handle: paginated-blog
title: Paginated blog
url: /blogs/paginated-blog
articles:
- id: 6
title: 'One thing you probably did not know yet...'
author: Justin
content: Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
created_at: 2005-04-04 16:00
- id: 7
title: Fascinating
author: Tobi
content: Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
created_at: 2005-04-06 12:00
articles_count: 200
BIN
View File
Binary file not shown.
+330
View File
@@ -0,0 +1,330 @@
# frozen_string_literal: true
require 'pry-byebug'
require 'set'
module Liquid
class Compiler
def initialize
@ruby = +""
@nodes = {}
@declared = Set.new(['__l_product'])
end
def <<(line)
@ruby << line
end
def var_name(name)
"__l_#{name}"
end
def declare(name)
@declared << name
end
def declared?(name)
@declared.member?(name)
end
def to_proc
show_ruby = ENV["SHOW_RUBY"] && ENV["SHOW_RUBY"].to_i || 0
STDERR.puts @ruby if show_ruby >= 1
res = RubyVM::InstructionSequence.compile(<<~RUBY).eval.call(@nodes)
# frozen_string_literal: true
->(__nodes) {
->(__context, __output, __l_product) {
__assigns = __context.scopes.last
#{@ruby}
__output
}
}
RUBY
STDERR.puts RubyVM::InstructionSequence.disasm(res) if show_ruby >= 2
res
end
def compile_expr(node)
if node.respond_to?(:compile_expr)
node.compile_expr(self)
else
case node
when Range # returned by RangeLookup when range contains only literals
node.inspect
when Integer, Float, nil, true, false, String
node.inspect
else
raise ArgumentError, "cannot compile node #{node.inspect}"
end
end
end
def output(node)
compiled = compile_expr(node)
self << if compiled.is_a?(String)
"__output << #{compiled}\n"
else
"__output << #{compiled}.to_s\n"
end
end
def fallback_evaluate_expr(node)
"__nodes[#{node.object_id}].evaluate(__context)"
end
def to_integer_expr(var_name)
<<~RUBY.strip
(begin
if #{var_name}.is_a?(Integer)
#{var_name}
else
begin
Integer(#{var_name}.to_s)
rescue ::ArgumentError
raise Liquid::ArgumentError, "invalid integer"
end
end
end)
RUBY
end
def compile(node)
if node.instance_of?(String)
self << "__output << #{node.inspect}\n"
else
@nodes[node.object_id] = node
if node.respond_to?(:compile)
node.compile(self)
else
line_number = if node.respond_to?(:line_number) && node.line_number.is_a?(Integer)
node.line_number
else
nil
end
catch_errors(line_number, show_message: !node.blank?) do
self << "__nodes[#{node.object_id}].render_to_output_buffer(__context, __output) # #{node.inspect} \n"
end
end
end
end
def catch_errors(line_number, show_message: true)
self << "begin\n"
yield
self << <<~RUBY
rescue => __exc
case __exc
when Liquid::MemoryError
raise
when Liquid::UndefinedVariable, Liquid::UndefinedDropMethod, Liquid::UndefinedFilter
__context.handle_error(__exc, #{line_number.inspect})
else
__error_message = __context.handle_error(__exc, #{line_number.inspect})
RUBY
self << "__output << __error_message\n" if show_message
self << "end\nend\n"
end
end
class BlockBody
def render_to_output_buffer(context, output)
raise "Tried to render uncompiled block" unless @compiled
@compiled.call(context, output, context.environments[0][:product])
end
def compile_top_level
compiler = Compiler.new
compile(compiler)
@compiled = compiler.to_proc
end
def compile(compiler)
nodelist.each { |node| compiler.compile(node) }
end
end
class Document
def parse(tokenizer, parse_context)
while parse_body(tokenizer)
end
@body.compile_top_level
@body.freeze
rescue SyntaxError => e
e.line_number ||= parse_context.line_number
raise
end
end
class VariableLookup
def compile_expr(compiler)
# HACK
if @name == "forloop" && @lookups == ["first"]
return "forloop_first"
end
var_name = compiler.var_name(@name)
root = if compiler.declared?(var_name)
var_name
else
"__scope[#{@name.inspect}]"
end
@lookups.reduce(root) do |prev, lookup|
"#{prev}[:#{lookup}]"
end
end
end
class Variable
FILTERS = {
"modulo" => ->(compiler, expr, args, kwargs) {
"(#{expr} % #{args[0]})"
},
"product_img_url" => ->(compiler, expr, args, kwargs) {
style = args.fetch(0, 'small')
rest = case style
when 'original'
"\"/files/shops/random_number/\#{url}\""
when 'grande', 'large', 'medium', 'compact', 'small', 'thumb', 'icon'
"\"/files/shops/random_number/products/\#{$1}_#{style}.\#{$2}\""
else
"raise ArgumentError, 'valid parameters for filter \"size\" are: original, grande, large, medium, compact, small, thumb and icon '"
end
<<~RUBY.strip
(begin
if #{expr} =~ %r{\\Aproducts/([\\w\\-\\_]+)\\.(\\w{2,4})}
#{rest}
else
raise ArgumentError, 'filter \"size\" can only be called on product images'
end
end)
RUBY
},
"escape" => ->(compiler, expr, args, kwargs) {
"(_t = #{expr}; CGI.escapeHTML(_t) if _t)"
},
"money" => ->(compiler, expr, args, kwargs) {
"(_m = #{expr}; _m.nil? ? '' : \"$ \#{(_m / 100.0).round(2)}\")"
},
}
def compile(compiler)
if const?
compiler.output(name)
else
compiler.catch_errors(@line_number, show_message: true) do
compiler.output(self)
end
end
end
def const?
@filters.empty? && !name.respond_to?(:compile_expr)
end
def compile_expr(compiler)
@filters.reduce(compiler.compile_expr(name)) do |expr, (name, args, kwargs)|
if FILTERS.key?(name)
FILTERS[name].call(compiler, expr, args, kwargs)
else
expr
end
end
end
end
class Echo
def compile(compiler)
compiler.compile(variable)
end
end
class For
def compile(compiler)
compiler << "collection = #{compiler.compile_expr(@collection_name)}\n"
unless @from.nil?
compiler << <<~RUBY
from_value = #{compiler.compile_expr(@from)}
from = if from_value.nil?
0
else
#{compiler.to_integer_expr(:from_value)}
end
limit_value = #{compiler.compile_expr(@limit)}
to = if limit_value.nil?
nil
else
#{compiler.to_integer_expr(:limit_value)} + from
end
collection = #{compiler.slice_collection_expr(:collection, :from, :to)}
#{@reversed ? "segment.reverse!" : "" }
RUBY
end
item_var = compiler.var_name(variable_name)
compiler << <<~RUBY
forloop_first = true
for #{item_var} in collection
RUBY
compiler.declare(item_var)
compiler.compile(@for_block)
compiler << <<~RUBY
forloop_first = false
end
RUBY
end
end
class If
def compile(compiler)
compiler << "if #{compiler.compile_expr(blocks[0])}\n"
compiler.compile(blocks[0].attachment)
blocks.drop(1).each do |block|
if block.else?
compiler << "else\n"
else
compiler << "elsif #{block.compile_expr(compiler)}\n"
end
compiler.compile(block.attachment)
end
compiler << "end\n"
end
end
class Comment
def compile(_compiler); end
end
class Condition
def compile_expr(compiler)
if operator
left_expr = compiler.compile_expr(left)
right_expr = compiler.compile_expr(right)
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
left_expr = compiler.compile_expr(left)
expr = "#{left_expr}"
end
end
end
class Assign
def compile(compiler)
compiler.declare(compiler.var_name(@to))
compiler << "#{compiler.var_name(@to)} = #{compiler.compile_expr(@from)}\n"
end
end
end
+4
View File
@@ -25,6 +25,10 @@ module Liquid
start_int..end_int
end
def compile_expr(compiler)
compiler.fallback_evaluate_expr(self)
end
private
def to_integer(input)
+4
View File
@@ -93,6 +93,10 @@ module Liquid
context.apply_global_filter(obj)
end
def compile
"__output << #{@name}\n"
end
def render_to_output_buffer(context, output)
obj = render(context)
+224
View File
@@ -0,0 +1,224 @@
#!/usr/bin/env ruby
# frozen_string_literal: true
unless ENV.key?("BUNDLE_BIN_PATH")
exec("bundle", "exec", "ruby", __FILE__, *ARGV)
end
require "pry"
require "liquid"
require "unicode_plot"
require "optparse"
require "open3"
require "csv"
require "io/console"
TERM_ROWS, TERM_COLS = IO.console.winsize
def record
if ARGV.count != 2
STDERR.puts "Usage: benchmark.rb record [output_path]"
exit(1)
end
output_path = ARGV[1]
out, status = Open3.capture2("ruby", "#{__dir__}/benchmark_child.rb")
File.write(output_path, out)
end
def calc_stats(nums)
mean = nums.reduce(:+) / nums.length
variance = nums.map { |n| (n - mean).pow(2) }.reduce(:+) / nums.length
stddev = Math.sqrt(variance)
{
mean: mean,
variance: variance,
stddev: stddev,
normalized: normalize_outliers(mean, stddev, nums),
raw: nums
}
end
def normalize_outliers(mean, stddev, nums)
cutoff = stddev * 3
nums.map do |n|
if (n - mean).abs < cutoff
n
else
mean
end
end
end
def show
if ARGV.count < 2
STDERR.puts "Usage: benchmark.rb show [path1] [path2]? ..."
exit(1)
end
recordings = ARGV.drop(1).to_h do |path|
[File.basename(path), CSV.parse(
File.open(path),
col_sep: "\t",
headers: true,
converters: :integer
)]
end
runs = (1..1000).to_a
recordings.values.first.headers.each do |benchmark|
colors = [:green, :blue, :red]
10.times { puts }
title = "Benchmark: #{benchmark} (times in µs)"
print " " * (TERM_COLS / 2 - title.length)
puts title
puts
all_stats = recordings.transform_values do |csv|
stats = calc_stats(csv.map { |row| row[benchmark] })
stats[:color] = colors.shift
stats
end
line_plots = []
distributions = []
shared_line_plot = nil
max_mean_stats = all_stats.values.max_by { |stats| stats[:mean] }
max_y = (max_mean_stats[:mean] + max_mean_stats[:stddev] * 3).to_i
all_stats.each do |name, stats|
if shared_line_plot == nil
shared_line_plot = UnicodePlot.lineplot(
runs,
stats[:normalized],
name: name,
width: TERM_COLS - 25,
ylim: [0, max_y],
color: stats[:color]
)
else
UnicodePlot.lineplot!(shared_line_plot, stats[:normalized], name: name, color: stats[:color])
end
line_plots << render_to_s(UnicodePlot.lineplot(
runs,
stats[:normalized],
name: name,
color: stats[:color],
width: 40
))
distributions << render_to_s(UnicodePlot.histogram(
stats[:normalized],
title: name,
color: stats[:color]
))
end
shared_line_plot.render
print_columns(line_plots)
print_columns(distributions)
all_times = all_stats.transform_values { |stats| stats[:normalized] }
UnicodePlot.boxplot(data: all_times, title: "Comparison", width: TERM_COLS - 25).render
end
end
def render_to_s(plot)
io = StringIO.new
plot.render(io, color: true)
io.string
end
def visual_length(line)
line.gsub(/\e\[(\d+)m/, '').length
end
def print_columns(cols)
col_lines = cols.map { |col| col.split("\n") }
col_width = col_lines.map do |lines|
lines.map { |line| visual_length(line) }.max
end.max
col_height = col_lines.map { |lines| lines.length }.max
(0...col_height).each do |i|
col_lines.each do |lines|
line = lines[i] || ""
vis_length = visual_length(line)
print line
print(" " * (col_width - vis_length))
end
puts
end
end
ITERS = 1
Benchmarks = Class.new do
def define(_name, benchmark)
@benchmark = benchmark
end
def run
start = Process.clock_gettime(Process::CLOCK_MONOTONIC, :microsecond)
@benchmark.compile
parsed = Process.clock_gettime(Process::CLOCK_MONOTONIC, :microsecond)
# warmup
ITERS.times { @benchmark.render}
warm = Process.clock_gettime(Process::CLOCK_MONOTONIC, :microsecond)
res = nil
ITERS.times { res = @benchmark.render }
ran = Process.clock_gettime(Process::CLOCK_MONOTONIC, :microsecond)
if res.is_a?(String)
puts res
puts "Output digest: #{Digest::SHA2.hexdigest(res)}"
end
if ENV['SHOW_RUBY'] == '1'
STDERR.puts "note: SHOW_RUBY prevents gathering parse metrics"
STDERR.puts "render: #{(ran - warm) / ITERS}µs"
else
STDERR.puts "parse: #{parsed - start}µs"
STDERR.puts "render: #{(ran - warm) / ITERS}µs"
STDERR.puts "total: #{(ran - warm) / ITERS + (parsed - start)}µs"
end
end
end.new
def execute
require 'digest'
if ARGV.count != 2
STDERR.puts "Usage: benchmark.rb record [output_path]"
exit(1)
end
case ENV['ENGINE']
when 'LIQUID_COMPILE'
require_relative '../lib/liquid/compile'
when 'LIQUID_C'
require 'liquid/c'
when 'LIQUID_RUBY'
else
raise "Invalid ENGINE: #{ENV['ENGINE'].inspect}, expected ENGINE=(LIQUID_RUBY|LIQUID_COMPILE)"
end
require_relative "./benchmarks/#{ARGV[1]}.rb"
Benchmarks.run
end
case ARGV.first
when "record", "r"
record
when "show", "s"
show
when "execute", "x"
execute
else
puts "Invalid command. Expected benchmark [record|show|execute] ..."
exit 1
end
-20
View File
@@ -1,20 +0,0 @@
# frozen_string_literal: true
require 'benchmark/ips'
require_relative 'theme_runner'
Liquid::Template.error_mode = ARGV.first.to_sym if ARGV.first
profiler = ThemeRunner.new
Benchmark.ips do |x|
x.time = 10
x.warmup = 5
puts
puts "Running benchmark for #{x.time} seconds (with #{x.warmup} seconds warmup)."
puts
x.report("parse:") { profiler.compile }
x.report("render:") { profiler.render }
x.report("parse & render:") { profiler.run }
end
+62
View File
@@ -0,0 +1,62 @@
# frozen_string_literal: true
require 'liquid'
Liquid::Template.error_mode = :strict
case ENV['ENGINE']
when 'LIQUID_COMPILE'
require_relative '../lib/liquid/compile'
when 'LIQUID_C'
require 'liquid/c'
when 'LIQUID_RUBY'
else
raise "Invalid engine: #{ENV['ENGINE'].inspect}"
end
OPTIONS = {
render_iters: 1000
}
def get_time_us
Process.clock_gettime(Process::CLOCK_MONOTONIC, :microsecond)
end
Benchmarks = Class.new do
def initialize
@by_name = {}
end
def define(name, benchmark)
@by_name[name] = benchmark
end
def run
times = {}
@by_name.each do |name, benchmark|
benchmark.compile
times[name] = OPTIONS[:render_iters].times.map do
before = get_time_us
benchmark.render
get_time_us - before
end
end
puts times.keys.join("\t")
cols = times.values
OPTIONS[:render_iters].times do |i|
cols.each do |values|
print values[i]
print "\t" unless values == cols.last
end
puts
end
end
end.new
Dir[__dir__ + "/benchmarks/*.rb", base: __dir__].each do |path|
require_relative path
end
Benchmarks.run
+28
View File
@@ -0,0 +1,28 @@
# frozen_string_literal: true
Benchmarks.define('fizzbuzz_10000', Class.new do
TEMPLATE = <<~LIQUID
{% for i in (1..#{ENV['COUNT'].to_i}) %}
{% liquid
assign rem_3 = i | modulo: 3
assign rem_5 = i | modulo: 5
if rem_3 == 0 and rem_5 == 0
echo "Fizzbuzz"
elsif rem_3 == 0
echo "Fizz"
elsif rem_5 == 0
echo "Buzz"
else
echo i
endif
%}{% endfor %}
LIQUID
def compile
@parsed = Liquid::Template.parse(TEMPLATE)
end
def render
@parsed.render
end
end.new)
+13
View File
@@ -0,0 +1,13 @@
# frozen_string_literal: true
Benchmarks.define('simple_loop', Class.new do
TEMPLATE = "{% for i in (1..1000) %}{{ i }}{% endfor %}"
def compile
@parsed = Liquid::Template.parse(TEMPLATE)
end
def render
@parsed.render
end
end.new)
+5
View File
@@ -0,0 +1,5 @@
# frozen_string_literal: true
require_relative '../theme_runner'
#Benchmarks.define('theme_runner', ThemeRunner.new)
+10
View File
@@ -3,6 +3,16 @@
require 'stackprof'
require_relative 'theme_runner'
if ENV['LIQUID_C'] == '1'
puts "-- LIQUID C"
require 'liquid/c'
end
if ENV['LIQUID_COMPILE'] == '1'
puts "-- COMPILED"
require 'liquid/compile'
end
Liquid::Template.error_mode = ARGV.first.to_sym if ARGV.first
profiler = ThemeRunner.new
profiler.run
-2
View File
@@ -1,8 +1,6 @@
# frozen_string_literal: true
$LOAD_PATH.unshift(__dir__ + '/../../lib')
require_relative '../../lib/liquid'
require_relative 'comment_form'
require_relative 'paginate'
require_relative 'json_filter'
+4 -1
View File
@@ -15,7 +15,10 @@ if (env_mode = ENV['LIQUID_PARSER_MODE'])
end
Liquid::Template.error_mode = mode
if ENV['LIQUID_C'] == '1'
if ENV['LIQUID_COMPILE'] == '1'
puts "-- COMPILED"
require 'liquid/compile'
elsif ENV['LIQUID_C'] == '1'
puts "-- LIQUID C"
require 'liquid/c'
end