Shopify/liquid — pull request #380 by jasonhl (2014-07-15 18:54:21+00:00)
Title: Optimize checking for interrupts by replacing any? with NOT empty?

Doing an object allocation profile on Shopify in production using stackprof reveals that the number three generator of objects is Liquid#context has_interrupt?

The method is called in the main flow of the block rendering, which means it is called a lot. The documentation for .any? (http://ruby-doc.org/core-2.1.2/Enumerable.html#method-i-any-3F) states that:

```
Passes each element of the collection to the given block.
The method returns true if the block ever returns a value other than false or nil.
If the block is not given, Ruby adds an implicit block of { |obj| obj } that will cause
any? to return true if at least one of the collection members is not false or nil.
```

Note the 'implicit block' bit -- that's where the allocation comes from.

Changed this to !empty, which is equivalent if (and only if) the array in question cannot contain nil/false values. The `@interrupts` member is only altered by push_interrupt, which is only ever called with a the keywords break or continue, so this is perfectly safe.

The existing tests appear to cover both the break and continue tags, so I did not add any as this should not alter behaviour in any way.

I temporarily altered the the code in profile.rb to benchmark object allocation.

Here's before:
```
vagrant@vagrant:~/src/liquid/performance$ bundle exec ruby profile.rb
==================================
Mode: object(1)
Samples: 9639300 (0.00% miss rate)
GC: 0 (0.00%)
==================================
TOTAL (pct) SAMPLES (pct) FRAME
1947200 (20.2%) 1462300 (15.2%) Liquid::Context#variable
2185900 (22.7%) 1433400 (14.9%) Liquid::Variable#lax_parse
833800 (8.7%) 833800 (8.7%) Liquid::Context#has_interrupt?
10194700 (105.8%) 806100 (8.4%) Liquid::Block#parse
752500 (7.8%) 752500 (7.8%) block in Liquid::Variable#lax_parse
2944500 (30.5%) 521600 (5.4%) Liquid::Block#create_variable
438600 (4.6%) 438600 (4.6%) Liquid::Template#tokenize
480300 (5.0%) 433300 (4.5%) Liquid::Context#find_variable
2368300 (24.6%) 421100 (4.4%) Liquid::Context#resolve
3075100 (31.9%) 300600 (3.1%) Liquid::Variable#render
1075400 (11.2%) 295500 (3.1%) block in Liquid::Variable#render
289100 (3.0%) 289100 (3.0%) Liquid::If#lax_parse
2422900 (25.1%) 238800 (2.5%) block in Liquid::Block#create_variable
204800 (2.1%) 204800 (2.1%) Liquid::StandardFilters#truncatewords
146100 (1.5%) 143500 (1.5%) Liquid::For#lax_parse
10927100 (113.4%) 108700 (1.1%) Liquid::Block#render_all
600600 (6.2%) 98500 (1.0%) Liquid::Context#invoke
70700 (0.7%) 70700 (0.7%) Liquid::Block#block_delimiter
48300 (0.5%) 48000 (0.5%) Liquid::Context#initialize
10764400 (111.7%) 47600 (0.5%) Liquid::Tag.parse
```

Here's with the change in this PR:
```
vagrant@vagrant:~/src/liquid/performance$ bundle exec ruby profile.rb
==================================
Mode: object(1)
Samples: 8805500 (0.00% miss rate)
GC: 0 (0.00%)
==================================
TOTAL (pct) SAMPLES (pct) FRAME
1947200 (22.1%) 1462300 (16.6%) Liquid::Context#variable
2185900 (24.8%) 1433400 (16.3%) Liquid::Variable#lax_parse
10194700 (115.8%) 806100 (9.2%) Liquid::Block#parse
752500 (8.5%) 752500 (8.5%) block in Liquid::Variable#lax_parse
2944500 (33.4%) 521600 (5.9%) Liquid::Block#create_variable
438600 (5.0%) 438600 (5.0%) Liquid::Template#tokenize
480300 (5.5%) 433300 (4.9%) Liquid::Context#find_variable
2368300 (26.9%) 421100 (4.8%) Liquid::Context#resolve
3075100 (34.9%) 300600 (3.4%) Liquid::Variable#render
1075400 (12.2%) 295500 (3.4%) block in Liquid::Variable#render
289100 (3.3%) 289100 (3.3%) Liquid::If#lax_parse
2422900 (27.5%) 238800 (2.7%) block in Liquid::Block#create_variable
204800 (2.3%) 204800 (2.3%) Liquid::StandardFilters#truncatewords
146100 (1.7%) 143500 (1.6%) Liquid::For#lax_parse
9043900 (102.7%) 108700 (1.2%) Liquid::Block#render_all
600600 (6.8%) 98500 (1.1%) Liquid::Context#invoke
70700 (0.8%) 70700 (0.8%) Liquid::Block#block_delimiter
48300 (0.5%) 48000 (0.5%) Liquid::Context#initialize
10764400 (122.2%) 47600 (0.5%) Liquid::Tag.parse
47000 (0.5%) 47000 (0.5%) block in Liquid::Context#find_variable
```

833,800 (8.7%) less objects allocated :-)

@camilo, @boourns, @dylanahsmith: Code review, please.
CC: @csfrancis