Skip to content

Commit

Permalink
Improve parsing performance (#719)
Browse files Browse the repository at this point in the history
  • Loading branch information
sebastienros authored Nov 24, 2024
1 parent 0e8a243 commit 068dc2d
Show file tree
Hide file tree
Showing 5 changed files with 68 additions and 41 deletions.
2 changes: 1 addition & 1 deletion Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
<PackageVersion Include="System.Reflection.Emit.Lightweight" Version="4.7.0" />

<!-- Common to all TFMs -->
<PackageVersion Include="Parlot" Version="1.0.2" />
<PackageVersion Include="Parlot" Version="1.1.0" />
<PackageVersion Include="TimeZoneConverter" Version="6.1.0" />

<!-- Benchmarks -->
Expand Down
2 changes: 1 addition & 1 deletion Fluid.Benchmarks/FluidBenchmarks.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

namespace Fluid.Benchmarks
{
[MemoryDiagnoser]
[MemoryDiagnoser, ShortRunJob]
public class FluidBenchmarks : BaseBenchmarks
{
private readonly TemplateOptions _options = new TemplateOptions();
Expand Down
6 changes: 3 additions & 3 deletions Fluid/Parser/IdentifierParser.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using Parlot;
using Parlot;
using Parlot.Fluent;

namespace Fluid.Parser
Expand Down Expand Up @@ -35,7 +35,7 @@ public override bool Parse(ParseContext context, ref ParseResult<TextSpan> resul

cursor.Advance();

while (!context.Scanner.Cursor.Eof)
while (!cursor.Eof)
{
current = cursor.Current;

Expand Down Expand Up @@ -93,4 +93,4 @@ private static bool IsNonDigitStart(char ch)
(ch == '_')
;
}
}
}
35 changes: 30 additions & 5 deletions Fluid/Parser/TagParsers.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using Fluid.Ast;
using Fluid.Ast;
using Parlot;
using Parlot.Fluent;
using Parlot.Rewriting;

namespace Fluid.Parser
{
Expand Down Expand Up @@ -36,7 +37,7 @@ public static class TagParsers
public static Parser<TagResult> OutputTagStart(bool skipWhiteSpace = false) => new OutputTagStartParser(skipWhiteSpace);
public static Parser<TagResult> OutputTagEnd(bool skipWhiteSpace = false) => new OutputTagEndParser(skipWhiteSpace);

private sealed class TagStartParser : Parser<TagResult>
private sealed class TagStartParser : Parser<TagResult>, ISeekable
{
private readonly bool _skipWhiteSpace;

Expand All @@ -45,6 +46,12 @@ public TagStartParser(bool skipWhiteSpace = false)
_skipWhiteSpace = skipWhiteSpace;
}

public bool CanSeek => true;

public char[] ExpectedChars => ['{'];

public bool SkipWhitespace => _skipWhiteSpace;

public override bool Parse(ParseContext context, ref ParseResult<TagResult> result)
{
if (_skipWhiteSpace)
Expand Down Expand Up @@ -90,7 +97,7 @@ public override bool Parse(ParseContext context, ref ParseResult<TagResult> resu
}
}

private sealed class TagEndParser : Parser<TagResult>
private sealed class TagEndParser : Parser<TagResult>, ISeekable
{
private readonly bool _skipWhiteSpace;

Expand All @@ -99,6 +106,12 @@ public TagEndParser(bool skipWhiteSpace = false)
_skipWhiteSpace = skipWhiteSpace;
}

public bool CanSeek => true;

public char[] ExpectedChars => ['-', '}'];

public bool SkipWhitespace => _skipWhiteSpace;

public override bool Parse(ParseContext context, ref ParseResult<TagResult> result)
{
var p = (FluidParseContext)context;
Expand Down Expand Up @@ -183,7 +196,7 @@ public override bool Parse(ParseContext context, ref ParseResult<TagResult> resu
}
}

private sealed class OutputTagStartParser : Parser<TagResult>
private sealed class OutputTagStartParser : Parser<TagResult>, ISeekable
{
private readonly bool _skipWhiteSpace;

Expand All @@ -192,6 +205,12 @@ public OutputTagStartParser(bool skipWhiteSpace = false)
_skipWhiteSpace = skipWhiteSpace;
}

public bool CanSeek => true;

public char[] ExpectedChars => ['{'];

public bool SkipWhitespace => _skipWhiteSpace;

public override bool Parse(ParseContext context, ref ParseResult<TagResult> result)
{
if (_skipWhiteSpace)
Expand Down Expand Up @@ -231,7 +250,7 @@ public override bool Parse(ParseContext context, ref ParseResult<TagResult> resu
}
}

private sealed class OutputTagEndParser : Parser<TagResult>
private sealed class OutputTagEndParser : Parser<TagResult>, ISeekable
{
private readonly bool _skipWhiteSpace;

Expand All @@ -240,6 +259,12 @@ public OutputTagEndParser(bool skipWhiteSpace = false)
_skipWhiteSpace = skipWhiteSpace;
}

public bool CanSeek => true;

public char[] ExpectedChars => ['-', '}'];

public bool SkipWhitespace => _skipWhiteSpace;

public override bool Parse(ParseContext context, ref ParseResult<TagResult> result)
{
if (_skipWhiteSpace)
Expand Down
64 changes: 33 additions & 31 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -1077,40 +1077,42 @@ Run it locally to analyze the time it takes to execute specific templates.
#### Results

Fluid is faster and allocates less memory than all other well-known .NET Liquid parsers.
For parsing, Fluid is 19% faster than the second, Scriban, allocating nearly 3 times less memory.
For rendering, Fluid is 26% faster than the second, Handlebars, 5 times faster than Scriban, but allocates half the memory.
Compared to DotLiquid, Fluid renders 11 times faster, and allocates 35 times less memory.
For parsing, Fluid is 20% faster than the second, Scriban, allocating 2 times less memory.
For rendering, Fluid is 30% faster than the second, Handlebars, allocating half the memory, and 5 times faster than Scriban.
Compared to DotLiquid, Fluid renders 10 times faster, and allocates 34 times less memory.

``` text
BenchmarkDotNet v0.14.0, Windows 11 (10.0.26100.2033)
BenchmarkDotNet v0.14.0, Windows 11 (10.0.26100.2314)
12th Gen Intel Core i7-1260P, 1 CPU, 16 logical and 12 physical cores
.NET SDK 9.0.100-rc.2.24474.11
[Host] : .NET 8.0.10 (8.0.1024.46610), X64 RyuJIT AVX2
DefaultJob : .NET 8.0.10 (8.0.1024.46610), X64 RyuJIT AVX2
| Method | Mean | Error | StdDev | Ratio | RatioSD | Gen0 | Gen1 | Gen2 | Allocated | Alloc Ratio |
|------------------- |--------------:|------------:|------------:|-------:|--------:|----------:|---------:|--------:|------------:|------------:|
| Fluid_Parse | 3.393 us | 0.0628 us | 0.0524 us | 1.00 | 0.02 | 0.3052 | - | - | 2.81 KB | 1.00 |
| Scriban_Parse | 3.785 us | 0.0696 us | 0.1063 us | 1.12 | 0.04 | 0.7744 | 0.0267 | - | 7.14 KB | 2.54 |
| DotLiquid_Parse | 7.339 us | 0.1385 us | 0.1228 us | 2.16 | 0.05 | 1.7395 | - | - | 16.21 KB | 5.76 |
| LiquidNet_Parse | 28.002 us | 0.5425 us | 0.6663 us | 8.25 | 0.23 | 6.7444 | 0.6104 | - | 62.04 KB | 22.06 |
| Handlebars_Parse | 2,597.261 us | 30.8705 us | 27.3659 us | 765.59 | 13.89 | 15.6250 | 7.8125 | - | 156.37 KB | 55.60 |
| | | | | | | | | | | |
| Fluid_ParseBig | 17.882 us | 0.2029 us | 0.1584 us | 1.00 | 0.01 | 1.2512 | 0.0305 | - | 11.64 KB | 1.00 |
| Scriban_ParseBig | 19.891 us | 0.3979 us | 0.3907 us | 1.11 | 0.02 | 3.4790 | 0.4883 | - | 32.07 KB | 2.75 |
| DotLiquid_ParseBig | 30.766 us | 0.6128 us | 1.0069 us | 1.72 | 0.06 | 10.2539 | 0.4883 | - | 94.36 KB | 8.11 |
| LiquidNet_ParseBig | 14,207.006 us | 347.1824 us | 984.8987 us | 794.52 | 55.23 | 3093.7500 | 15.6250 | - | 28543.38 KB | 2,452.05 |
| | | | | | | | | | | |
| Fluid_Render | 158.640 us | 3.1074 us | 7.4451 us | 1.00 | 0.06 | 10.2539 | 0.4883 | - | 95.87 KB | 1.00 |
| Handlebars_Render | 216.572 us | 4.2552 us | 9.1598 us | 1.37 | 0.08 | 20.9961 | 3.4180 | - | 194.92 KB | 2.03 |
| Scriban_Render | 768.660 us | 14.5379 us | 29.3673 us | 4.86 | 0.28 | 68.3594 | 68.3594 | 68.3594 | 498.65 KB | 5.20 |
| LiquidNet_Render | 1,073.246 us | 20.1804 us | 21.5928 us | 6.78 | 0.33 | 339.8438 | 160.1563 | - | 3130.83 KB | 32.66 |
| DotLiquid_Render | 1,812.898 us | 52.1755 us | 148.8597 us | 11.45 | 1.07 | 351.5625 | 140.6250 | 23.4375 | 3368.09 KB | 35.13 |
```

Tested on May 28, 2024 with
- Scriban 5.11.0
.NET SDK 9.0.100
[Host] : .NET 9.0.0 (9.0.24.52809), X64 RyuJIT AVX2
ShortRun : .NET 9.0.0 (9.0.24.52809), X64 RyuJIT AVX2
Job=ShortRun IterationCount=3 LaunchCount=1
WarmupCount=3
| Method | Mean | Error | StdDev | Ratio | Allocated | Alloc Ratio |
|------------------- |--------------:|--------------:|------------:|---------:|------------:|------------:|
| Fluid_Parse | 2.622 us | 1.4586 us | 0.0800 us | 1.00 | 2.83 KB | 1.00 |
| Scriban_Parse | 3.149 us | 0.8304 us | 0.0455 us | 1.20 | 7.14 KB | 2.53 |
| DotLiquid_Parse | 6.133 us | 1.5094 us | 0.0827 us | 2.34 | 16.21 KB | 5.73 |
| LiquidNet_Parse | 23.112 us | 6.0582 us | 0.3321 us | 8.82 | 62.04 KB | 21.94 |
| Handlebars_Parse | 2,662.991 us | 4,830.0818 us | 264.7531 us | 1,016.17 | 155.42 KB | 54.95 |
| | | | | | | |
| Fluid_ParseBig | 10.642 us | 2.0982 us | 0.1150 us | 1.00 | 11.66 KB | 1.00 |
| Scriban_ParseBig | 18.546 us | 14.2197 us | 0.7794 us | 1.74 | 32.07 KB | 2.75 |
| DotLiquid_ParseBig | 25.980 us | 8.1228 us | 0.4452 us | 2.44 | 94.36 KB | 8.10 |
| LiquidNet_ParseBig | 11,175.713 us | 5,605.1094 us | 307.2350 us | 1,050.22 | 28542.56 KB | 2,448.69 |
| | | | | | | |
| Fluid_Render | 127.984 us | 46.8250 us | 2.5666 us | 1.00 | 95.87 KB | 1.00 |
| Scriban_Render | 601.083 us | 86.9414 us | 4.7656 us | 4.70 | 498.66 KB | 5.20 |
| DotLiquid_Render | 1,248.906 us | 231.9350 us | 12.7131 us | 9.76 | 3270.3 KB | 34.11 |
| LiquidNet_Render | 903.463 us | 2,324.0151 us | 127.3871 us | 7.06 | 3126.47 KB | 32.61 |
| Handlebars_Render | 170.182 us | 30.0175 us | 1.6454 us | 1.33 | 194.92 KB | 2.03 |
```

Tested on November 24, 2024 with
- Scriban 5.12.0
- DotLiquid 2.2.692
- Liquid.NET 0.10.0
- Handlebars.Net 2.1.6
Expand Down

0 comments on commit 068dc2d

Please sign in to comment.