Shopify/liquid — pull request #384 by jasonhl (2014-07-21 19:51:37+00:00)
Title: Optimize block parsing -- don't recreate delimiter, use strings instead of regex

Block parsing is a very hot path in both the Liquid benchmark and Shopify itself.

Two optimizations in this change:
* Created the block_delimiter in the constructor, so it does not have to be recreated on every parsing pass (saves useless object creation)
* Changed parsing to use string comparison instead of regexp -- the regexps being used were simply "\A\{\{" and "\A\{\%". That's something String.start_with? can handle much faster and without creating temporary objects.

Benchmark of master (best of 3 runs):
```
user system total real
parse: 2.120000 0.000000 2.120000 ( 2.119865)
parse & run: 4.410000 0.020000 4.430000 ( 4.422620)
```

Object allocation profile for master:
```
==================================
Mode: object(1)
Samples: 8171200 (0.00% miss rate)
GC: 0 (0.00%)
==================================
TOTAL (pct) SAMPLES (pct) FRAME
2185900 (26.8%) 1433400 (17.5%) Liquid::Variable#lax_parse
10194700 (124.8%) 806100 (9.9%) Liquid::Block#parse
752500 (9.2%) 752500 (9.2%) block in Liquid::Variable#lax_parse
625200 (7.7%) 625200 (7.7%) Liquid::Context#variable_parse
2944500 (36.0%) 521600 (6.4%) Liquid::Block#create_variable
438600 (5.4%) 438600 (5.4%) Liquid::Template#tokenize
480300 (5.9%) 433300 (5.3%) Liquid::Context#find_variable
1716000 (21.0%) 421100 (5.2%) Liquid::Context#resolve
2521700 (30.9%) 300600 (3.7%) Liquid::Variable#render
1053600 (12.9%) 295500 (3.6%) block in Liquid::Variable#render
289100 (3.5%) 289100 (3.5%) Liquid::If#lax_parse
2422900 (29.7%) 238800 (2.9%) block in Liquid::Block#create_variable
204800 (2.5%) 204800 (2.5%) Liquid::StandardFilters#truncatewords
146100 (1.8%) 143500 (1.8%) Liquid::For#lax_parse
131300 (1.6%) 131300 (1.6%) block in Liquid::Context#variable
7068300 (86.5%) 108700 (1.3%) Liquid::Block#render_all
600600 (7.4%) 98500 (1.2%) Liquid::Context#invoke
70700 (0.9%) 70700 (0.9%) Liquid::Block#block_delimiter
66300 (0.8%) 66000 (0.8%) Liquid::Context#initialize
683300 (8.4%) 58100 (0.7%) block in Liquid::Context#initialize
```

Benchmark of this PR (best of 3 runs):
```
user system total real
parse: 1.810000 0.000000 1.810000 ( 1.809644)
parse & run: 4.290000 0.000000 4.290000 ( 4.294896)

```

Object allocation profile for this PR:
```
==================================
Mode: object(1)
Samples: 7880000 (0.00% miss rate)
GC: 0 (0.00%)
==================================
TOTAL (pct) SAMPLES (pct) FRAME
2185900 (27.7%) 1433400 (18.2%) Liquid::Variable#lax_parse
752500 (9.5%) 752500 (9.5%) block in Liquid::Variable#lax_parse
625200 (7.9%) 625200 (7.9%) Liquid::Context#variable_parse
3019900 (38.3%) 597000 (7.6%) Liquid::Block#create_variable
9428200 (119.6%) 474300 (6.0%) Liquid::Block#parse
438600 (5.6%) 438600 (5.6%) Liquid::Template#tokenize
480300 (6.1%) 433300 (5.5%) Liquid::Context#find_variable
1716000 (21.8%) 421100 (5.3%) Liquid::Context#resolve
2521700 (32.0%) 300600 (3.8%) Liquid::Variable#render
1053600 (13.4%) 295500 (3.8%) block in Liquid::Variable#render
289100 (3.7%) 289100 (3.7%) Liquid::If#lax_parse
2422900 (30.7%) 238800 (3.0%) block in Liquid::Block#create_variable
204800 (2.6%) 204800 (2.6%) Liquid::StandardFilters#truncatewords
146100 (1.9%) 143500 (1.8%) Liquid::For#lax_parse
131300 (1.7%) 131300 (1.7%) block in Liquid::Context#variable
7068300 (89.7%) 108700 (1.4%) Liquid::Block#render_all
600600 (7.6%) 98500 (1.2%) Liquid::Context#invoke
66300 (0.8%) 66000 (0.8%) Liquid::Context#initialize
683300 (8.7%) 58100 (0.7%) block in Liquid::Context#initialize
53000 (0.7%) 53000 (0.7%) Liquid::Block#initialize
```

Down 291,200 objects and ~150ms.

@Shopify/liquid