Skip to content

Commit

Permalink
Fix performance degradation issues.
Browse files Browse the repository at this point in the history
```
$ ./vendor/bin/phpbench run benchmarks/EngineBenchmark.php --report=aggregate

PhpBench 0.15-dev (@git_version@). Running benchmarks.
Using configuration file: ./yay/phpbench.json

\EngineBenchmark
																		     |
benchMacroExpansion    I4 P0    25.371 25.114 (ms)    : 0.539ms 2.13%
benchMacroExpansion    I4 P1    59.729 59.920 (ms)    : 1.007ms 1.69%
benchMacroExpansion    I4 P2    104.914 104.161 (ms)  : 1.942ms 1.85%
benchMacroExpansion    I4 P3    163.220 163.037 (ms)  : 3.374ms 2.07%
benchMacroExpansion    I4 P4    313.258 313.639 (ms)  : 1.739ms 0.56%
benchMacroExpansion    I4 P5    468.592 464.465 (ms)  : 8.165ms 1.74%
benchMacroExpansion    I4 P6    826.266 822.542 (ms)  : 6.465ms 0.78%

1 subjects, 35 iterations, 7 revs, 0 rejects, 0 failures, 0 warnings
(best [mean] worst) = 24,912.000 [280,192.829 278,982.569] 26,429.000 (μs)
⅀T: 9,806,749.000μs μSD/r 3,318.799μs μRSD/r: 1.544%

+-----------------+---------------------+--------+------+-----+-----------+
| benchmark h | mem_peak     | best      | mean      | worst     | diff   |
+-----------------+---------------------+--------+------+-----+-----------+
| EngineBench | 19,495,632b  | 24.912ms  | 25.371ms  | 26.429ms  | 1.00x  |
| EngineBench | 45,612,440b  | 58.146ms  | 59.729ms  | 60.852ms  | 2.35x  |
| EngineBench | 81,132,472b  | 102.792ms | 104.914ms | 108.012ms | 4.14x  |
| EngineBench | 124,487,008b | 158.990ms | 163.220ms | 168.434ms | 6.43x  |
| EngineBench | 238,881,608b | 310.390ms | 313.258ms | 315.611ms | 12.35x |
| EngineBench | 52,887,416b  | 461.669ms | 468.592ms | 483.650ms | 18.47x |
| EngineBench | 90,814,552b  | 818.067ms | 826.266ms | 834.284ms | 32.57x |
+-----------------+---------------------+--------+------+-----+-----------+
```

The regression was caused by `midrule` parser backtracking at each token,
causing the `TokenStream` to be traversed **many** times each time there was
a backtrack 😱

The new `midrule` behavior - backtracking when anything but `Ast` is returned -
is correct, it was a bugfix. But apparently the non backtracking behavior was
being used as a feature while traversing source and expanding it.
  • Loading branch information
marcioAlmada committed Jun 26, 2018
1 parent d322056 commit a6a81a0
Showing 1 changed file with 97 additions and 104 deletions.
201 changes: 97 additions & 104 deletions src/Engine.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ final class Engine {
private
$blueContext,
$cycle,
$parser,
$expander,
$filename = ''
;

Expand All @@ -26,132 +26,125 @@ function __construct() {
$this->cycle = new Cycle;
$this->blueContext = new BlueContext;

$this->parser =
traverse
$macroParser =
consume
(
// this midrule is where the preprocessor does the expansion!
midrule(function(TokenStream $ts) {
$token = $ts->current();

while (null !== $token) {

$tstring = $token->value();

// skip when something looks like a new macro to be parsed
if (YAY_DOLLAR === $tstring && sigil_prefix()->parse($ts) instanceof Ast) break;

// here attempt to match and expand userland macros
// but just in case at least one macro passes the entry point heuristics
if (isset($this->literalHitMap[$tstring])) {
foreach ($this->literalHitMap[$tstring] as $directives) {
foreach ($directives as $directive) {
$directive->apply($ts, $this);
}
}
}
else if (isset($this->typeHitMap[$token->type()])) {
foreach ($this->typeHitMap[$token->type()] as $directives) {
foreach ($directives as $directive) {
$directive->apply($ts, $this);
}
}
}

$token = $ts->next();
}
})
,
// here we parse, compile and allocate new macros
consume
chain
(
chain
(
sigil(
token(T_STRING, 'macro')
,
optional
sigil(
token(T_STRING, 'macro')
,
optional
(
repeat
(
repeat
(
chain(token(':'), label()->as('tag'))
)
chain(token(':'), label()->as('tag'))
)
->as('tags')
)
->as('declaration')
,
commit
->as('tags')
)
->as('declaration')
,
commit
(
chain
(
chain
lookahead
(
lookahead
(
token('{')
)
,
commit
token('{')
)
,
commit
(
chain
(
chain
braces()->as('pattern')
,
token(T_SR)
,
optional
(
braces()->as('pattern')
,
token(T_SR)
,
optional
chain
(
chain
(
token(T_FUNCTION)->as('declaration')
,
parentheses()->as('args')
,
braces()->as('body')
,
token(T_SR)
)
token(T_FUNCTION)->as('declaration')
,
parentheses()->as('args')
,
braces()->as('body')
,
token(T_SR)
)
->as('compiler_pass')
,
braces()->as('expansion')
)
)
->as('body')
,
optional
(
token(';')
->as('compiler_pass')
,
braces()->as('expansion')
)
)
->as('macro')
->as('body')
,
optional
(
token(';')
)
)
->as('macro')
)
,
CONSUME_DO_TRIM
)
->onCommit(function(Ast $macroAst) {
,
CONSUME_DO_TRIM
)
->onCommit(function(Ast $macroAst) {

$scope = Map::fromEmpty();
$scope = Map::fromEmpty();

$tags = Map::fromValues(array_map(
function(Ast $node) :string { return (string) $node->{'* tag'}->token(); },
iterator_to_array($macroAst->{'* declaration tags'}->list())
));
$tags = Map::fromValues(array_map(
function(Ast $node) :string { return (string) $node->{'* tag'}->token(); },
iterator_to_array($macroAst->{'* declaration tags'}->list())
));

if ($tags->contains('grammar'))
$pattern = new GrammarPattern($macroAst->{'declaration'}[0]->line(), $macroAst->{'macro body pattern'}, $tags, $scope);
else
$pattern = new Pattern($macroAst->{'declaration'}[0]->line(), $macroAst->{'macro body pattern'}, $tags, $scope);
if ($tags->contains('grammar'))
$pattern = new GrammarPattern($macroAst->{'declaration'}[0]->line(), $macroAst->{'macro body pattern'}, $tags, $scope);
else
$pattern = new Pattern($macroAst->{'declaration'}[0]->line(), $macroAst->{'macro body pattern'}, $tags, $scope);

$compilerPass = new CompilerPass($macroAst->{'* macro body compiler_pass'});
$expansion = new Expansion($macroAst->{'macro body expansion'}, $tags, $scope);
$macro = new Macro($tags, $pattern, $compilerPass, $expansion);
$compilerPass = new CompilerPass($macroAst->{'* macro body compiler_pass'});
$expansion = new Expansion($macroAst->{'macro body expansion'}, $tags, $scope);
$macro = new Macro($tags, $pattern, $compilerPass, $expansion);

$this->registerDirective($macro);
$this->registerDirective($macro);

if ($macro->tags()->contains('global')) $this->globalDirectives[] = $macro;
})
)
if ($macro->tags()->contains('global')) $this->globalDirectives[] = $macro;
})
;

$this->expander = function(TokenStream $ts) use($macroParser) {
$token = $ts->current();
while ($token instanceof Token) {
$tstring = $token->value();

// here we attempt to parse, compile and allocate new macros
if (YAY_DOLLAR === $tstring) $macroParser->parse($ts);

// here attempt to match and expand userland macros
// but just in case at least one macro passes the entry point heuristics
if (isset($this->literalHitMap[$tstring])) {
foreach ($this->literalHitMap[$tstring] as $directives) {
foreach ($directives as $directive) {
$directive->apply($ts, $this);
}
}
}
else if (isset($this->typeHitMap[$token->type()])) {
foreach ($this->typeHitMap[$token->type()] as $directives) {
foreach ($directives as $directive) {
$directive->apply($ts, $this);
}
}
}

$token = $ts->next();
}
};
}

function registerDirective(Directive $directive) {
Expand Down Expand Up @@ -191,7 +184,7 @@ function expand(string $source, string $filename = '', int $gc = self::GC_ENGINE

$ts = TokenStream::{$filename && self::GC_ENGINE_ENABLED === $gc ? 'fromSource' : 'FromSourceWithoutOpenTag'}($source);

$this->parser->parse($ts);
($this->expander)($ts);
$expansion = (string) $ts;

if (self::GC_ENGINE_ENABLED === $gc) {
Expand Down

0 comments on commit a6a81a0

Please sign in to comment.