Skip to content

Commit 1b74ad6

Browse files
live627sanmai
andauthored
Improve performance (#216)
* Improve performance I have identified three main bottlenecks when profiling the code with xdebug: 1. `array_shift()`: The array of tokens ended up being renumbered on each iteration. So we instead set consumed tokens to `null`. 2. `mb_substr()`: Using mutltibyte here is unnecessary and overkill, when we can just operate directly on the string. 3. `Liquid::get()`: This one surprised me, but due to the number of calls, here it is. Shave off more processing time by accessing the config array directly. Co-authored-by: Alexey Kopytko <[email protected]>
1 parent 024e2eb commit 1b74ad6

File tree

4 files changed

+45
-14
lines changed

4 files changed

+45
-14
lines changed

src/Liquid/AbstractBlock.php

Lines changed: 31 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,15 @@ class AbstractBlock extends AbstractTag
3333
*/
3434
protected static $trimWhitespace = false;
3535

36+
37+
private ?string $whitespaceControl;
38+
39+
private ?Regexp $startRegexp;
40+
private ?Regexp $tagRegexp;
41+
private ?Regexp $variableStartRegexp;
42+
43+
private ?Regexp $variableRegexp;
44+
3645
/**
3746
* @return array
3847
*/
@@ -51,16 +60,25 @@ public function getNodelist()
5160
*/
5261
public function parse(array &$tokens)
5362
{
54-
$startRegexp = new Regexp('/^' . Liquid::get('TAG_START') . '/');
55-
$tagRegexp = new Regexp('/^' . Liquid::get('TAG_START') . Liquid::get('WHITESPACE_CONTROL') . '?\s*(\w+)\s*(.*?)' . Liquid::get('WHITESPACE_CONTROL') . '?' . Liquid::get('TAG_END') . '$/s');
56-
$variableStartRegexp = new Regexp('/^' . Liquid::get('VARIABLE_START') . '/');
63+
// Constructor is not reliably called by subclasses, so we need to ensure these are set
64+
$this->startRegexp ??= new Regexp('/^' . Liquid::get('TAG_START') . '/');
65+
$this->tagRegexp ??= new Regexp('/^' . Liquid::get('TAG_START') . Liquid::get('WHITESPACE_CONTROL') . '?\s*(\w+)\s*(.*?)' . Liquid::get('WHITESPACE_CONTROL') . '?' . Liquid::get('TAG_END') . '$/s');
66+
$this->variableStartRegexp ??= new Regexp('/^' . Liquid::get('VARIABLE_START') . '/');
67+
68+
$startRegexp = $this->startRegexp;
69+
$tagRegexp = $this->tagRegexp;
70+
$variableStartRegexp = $this->variableStartRegexp;
5771

5872
$this->nodelist = [];
5973

6074
$tags = Template::getTags();
6175

62-
while (count($tokens)) {
63-
$token = array_shift($tokens);
76+
for ($i = 0, $n = count($tokens); $i < $n; $i++) {
77+
if ($tokens[$i] === null) {
78+
continue;
79+
}
80+
$token = $tokens[$i];
81+
$tokens[$i] = null;
6482

6583
if ($startRegexp->match($token)) {
6684
$this->whitespaceHandler($token);
@@ -114,11 +132,13 @@ public function parse(array &$tokens)
114132
*/
115133
protected function whitespaceHandler($token)
116134
{
135+
$this->whitespaceControl ??= Liquid::get('WHITESPACE_CONTROL');
136+
117137
/*
118138
* This assumes that TAG_START is always '{%', and a whitespace control indicator
119139
* is exactly one character long, on a third position.
120140
*/
121-
if (mb_substr($token, 2, 1) === Liquid::get('WHITESPACE_CONTROL')) {
141+
if ($token[2] === $this->whitespaceControl) {
122142
$previousToken = end($this->nodelist);
123143
if (is_string($previousToken)) { // this can also be a tag or a variable
124144
$this->nodelist[key($this->nodelist)] = rtrim($previousToken);
@@ -129,7 +149,7 @@ protected function whitespaceHandler($token)
129149
* This assumes that TAG_END is always '%}', and a whitespace control indicator
130150
* is exactly one character long, on a third position from the end.
131151
*/
132-
self::$trimWhitespace = mb_substr($token, -3, 1) === Liquid::get('WHITESPACE_CONTROL');
152+
self::$trimWhitespace = $token[-3] === $this->whitespaceControl;
133153
}
134154

135155
/**
@@ -254,9 +274,10 @@ private function blockName()
254274
*/
255275
private function createVariable($token)
256276
{
257-
$variableRegexp = new Regexp('/^' . Liquid::get('VARIABLE_START') . Liquid::get('WHITESPACE_CONTROL') . '?(.*?)' . Liquid::get('WHITESPACE_CONTROL') . '?' . Liquid::get('VARIABLE_END') . '$/s');
258-
if ($variableRegexp->match($token)) {
259-
return new Variable($variableRegexp->matches[1]);
277+
$this->variableRegexp ??= new Regexp('/^' . Liquid::get('VARIABLE_START') . Liquid::get('WHITESPACE_CONTROL') . '?(.*?)' . Liquid::get('WHITESPACE_CONTROL') . '?' . Liquid::get('VARIABLE_END') . '$/s');
278+
279+
if ($this->variableRegexp->match($token)) {
280+
return new Variable($this->variableRegexp->matches[1]);
260281
}
261282

262283
throw new ParseException("Variable $token was not properly terminated");

src/Liquid/Tag/TagExtends.php

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,13 @@ private function findBlocks(array $tokens)
8080
$b = [];
8181
$name = null;
8282

83-
foreach ($tokens as $token) {
83+
for ($i = 0, $n = count($tokens); $i < $n; $i++) {
84+
if ($tokens[$i] === null) {
85+
continue;
86+
}
87+
$token = $tokens[$i];
88+
$tokens[$i] = null;
89+
8490
if ($blockstartRegexp->match($token)) {
8591
$name = $blockstartRegexp->matches[1];
8692
$b[$name] = [];

src/Liquid/Tag/TagRaw.php

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,12 @@ public function parse(array &$tokens)
3636

3737
$this->nodelist = [];
3838

39-
while (count($tokens)) {
40-
$token = array_shift($tokens);
39+
for ($i = 0, $n = count($tokens); $i < $n; $i++) {
40+
if ($tokens[$i] === null) {
41+
continue;
42+
}
43+
$token = $tokens[$i];
44+
$tokens[$i] = null;
4145

4246
if ($tagRegexp->match($token)) {
4347
// If we found the proper block delimiter just end parsing here and let the outer block proceed

src/Liquid/Template.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ public function setFileSystem(FileSystem $fileSystem)
8383
}
8484

8585
/**
86-
* @param array|Cache $cache
86+
* @param array|Cache|null $cache
8787
*
8888
* @throws \Liquid\Exception\CacheException
8989
*/

0 commit comments

Comments
 (0)