From 95e9fa5010ac53f35853e8cf06d9b077583febd9 Mon Sep 17 00:00:00 2001 From: Watson Date: Sun, 26 Sep 2021 01:51:56 +0900 Subject: [PATCH 1/2] Use `String#=~` and `Regexp.last_match` instead to retrieve the markup content MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit If the first value is only used obtained with String#scan, it will increase the performance if replace with `String#=~` and `Regexp.last_match`. ### Environment - MacBook Air (M1, 2020) - macOS 12.0 beta 7 - Apple M1 - Ruby 3.0.2 ### Test code ```ruby require 'benchmark/ips' WhitespaceControl = '-' VariableStart = /\{\{/ VariableEnd = /\}\}/ ContentOfVariable = /\A#{VariableStart}#{WhitespaceControl}?(.*?)#{WhitespaceControl}?#{VariableEnd}\z/om token = "{{item.product.featured_image | product_img_url: 'thumb' }}" Benchmark.ips do |x| x.report("String#scan") { token.scan(ContentOfVariable) {|content| break } } x.report("String#match") { m = token.match(ContentOfVariable); m[1] } x.report("String#=~") { token =~ ContentOfVariable; Regexp.last_match(1) } x.compare! end ``` ### Result ``` Warming up -------------------------------------- String#scan 135.724k i/100ms String#match 117.397k i/100ms String#=~ 151.637k i/100ms Calculating ------------------------------------- String#scan 1.351M (± 0.8%) i/s - 6.786M in 5.021955s String#match 1.169M (± 1.3%) i/s - 5.870M in 5.020429s String#=~ 1.520M (± 0.9%) i/s - 7.733M in 5.087427s Comparison: String#=~: 1520250.9 i/s String#scan: 1351399.0 i/s - 1.12x (± 0.00) slower String#match: 1169384.1 i/s - 1.30x (± 0.00) slower ``` --- lib/liquid/block_body.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/liquid/block_body.rb b/lib/liquid/block_body.rb index 76ab0a85..2921ce88 100644 --- a/lib/liquid/block_body.rb +++ b/lib/liquid/block_body.rb @@ -231,8 +231,8 @@ module Liquid end def create_variable(token, parse_context) - token.scan(ContentOfVariable) do |content| - markup = content.first + if token =~ ContentOfVariable + markup = Regexp.last_match(1) return Variable.new(markup, parse_context) end BlockBody.raise_missing_variable_terminator(token, parse_context) From ebdfdb80e5040d76118d91022a97b189f2be9a4b Mon Sep 17 00:00:00 2001 From: Watson Date: Sun, 26 Sep 2021 03:03:13 +0900 Subject: [PATCH 2/2] Detect quoted string using String#{start_with?, end_with?} to reduce Regexp#=== calling --- lib/liquid/expression.rb | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/lib/liquid/expression.rb b/lib/liquid/expression.rb index c2c27909..fcdd4d8a 100644 --- a/lib/liquid/expression.rb +++ b/lib/liquid/expression.rb @@ -10,21 +10,23 @@ module Liquid 'empty' => '' }.freeze - SINGLE_QUOTED_STRING = /\A\s*'(.*)'\s*\z/m - DOUBLE_QUOTED_STRING = /\A\s*"(.*)"\s*\z/m - INTEGERS_REGEX = /\A\s*(-?\d+)\s*\z/ - FLOATS_REGEX = /\A\s*(-?\d[\d\.]+)\s*\z/ + INTEGERS_REGEX = /\A(-?\d+)\z/ + FLOATS_REGEX = /\A(-?\d[\d\.]+)\z/ # Use an atomic group (?>...) to avoid pathological backtracing from # malicious input as described in https://github.com/Shopify/liquid/issues/1357 - RANGES_REGEX = /\A\s*\(\s*(?>(\S+)\s*\.\.)\s*(\S+)\s*\)\s*\z/ + RANGES_REGEX = /\A\(\s*(?>(\S+)\s*\.\.)\s*(\S+)\s*\)\z/ def self.parse(markup) + return nil unless markup + + markup = markup.strip + if (markup.start_with?('"') && markup.end_with?('"')) || + (markup.start_with?("'") && markup.end_with?("'")) + return markup[1..-2] + end + case markup - when nil - nil - when SINGLE_QUOTED_STRING, DOUBLE_QUOTED_STRING - Regexp.last_match(1) when INTEGERS_REGEX Regexp.last_match(1).to_i when RANGES_REGEX @@ -32,7 +34,6 @@ module Liquid when FLOATS_REGEX Regexp.last_match(1).to_f else - markup = markup.strip if LITERALS.key?(markup) LITERALS[markup] else