Skip to content

Commit 80d3449

Browse files
authored
Improve MemberExpression performance (#788)
* use array without IReadOnlyList dispatch cost
1 parent 9fa20a5 commit 80d3449

File tree

2 files changed

+26
-14
lines changed

2 files changed

+26
-14
lines changed

Fluid/Ast/MemberExpression.cs

+18-13
Original file line numberDiff line numberDiff line change
@@ -4,28 +4,33 @@ namespace Fluid.Ast
44
{
55
public sealed class MemberExpression : Expression
66
{
7-
public MemberExpression(MemberSegment segment)
7+
private readonly MemberSegment[] _segments;
8+
9+
public MemberExpression(MemberSegment segment) : this([segment])
10+
{
11+
}
12+
13+
public MemberExpression(IReadOnlyList<MemberSegment> segments) : this(segments as MemberSegment[] ?? segments.ToArray())
814
{
9-
Segments = [segment];
1015
}
1116

12-
public MemberExpression(IReadOnlyList<MemberSegment> segments)
17+
internal MemberExpression(MemberSegment[] segments)
1318
{
14-
Segments = segments ?? [];
19+
_segments = segments ?? [];
1520

16-
if (Segments.Count == 0)
21+
if (_segments.Length == 0)
1722
{
18-
throw new ArgumentException("At least one segment is required in a MemberExpression");
23+
ExceptionHelper.ThrowArgumentNullException(nameof(segments), "At least one segment is required in a MemberExpression");
1924
}
2025
}
2126

22-
public IReadOnlyList<MemberSegment> Segments { get; }
27+
public IReadOnlyList<MemberSegment> Segments => _segments;
2328

2429
public override ValueTask<FluidValue> EvaluateAsync(TemplateContext context)
2530
{
2631
// The first segment can only be an IdentifierSegment
2732

28-
var initial = Segments[0] as IdentifierSegment;
33+
var initial = _segments[0] as IdentifierSegment;
2934

3035
// Search the initial segment in the local scope first
3136

@@ -46,14 +51,14 @@ public override ValueTask<FluidValue> EvaluateAsync(TemplateContext context)
4651
value = context.Model;
4752
}
4853

49-
for (var i = start; i < Segments.Count; i++)
54+
for (var i = start; i < _segments.Length; i++)
5055
{
51-
var s = Segments[i];
56+
var s = _segments[i];
5257
var task = s.ResolveAsync(value, context);
5358

5459
if (!task.IsCompletedSuccessfully)
5560
{
56-
return Awaited(task, context, Segments, i + 1);
61+
return Awaited(task, context, _segments, i + 1);
5762
}
5863

5964
value = task.Result;
@@ -71,11 +76,11 @@ public override ValueTask<FluidValue> EvaluateAsync(TemplateContext context)
7176
private static async ValueTask<FluidValue> Awaited(
7277
ValueTask<FluidValue> task,
7378
TemplateContext context,
74-
IReadOnlyList<MemberSegment> segments,
79+
MemberSegment[] segments,
7580
int startIndex)
7681
{
7782
var value = await task;
78-
for (var i = startIndex; i < segments.Count; i++)
83+
for (var i = startIndex; i < segments.Length; i++)
7984
{
8085
var s = segments[i];
8186
value = await s.ResolveAsync(value, context);

Fluid/ExceptionHelper.cs

+8-1
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,13 @@ public static void ThrowArgumentOutOfRangeException(string paramName, string mes
2929
throw new ArgumentOutOfRangeException(paramName, message);
3030
}
3131

32+
[DoesNotReturn]
33+
[MethodImpl(MethodImplOptions.NoInlining)]
34+
public static void ThrowArgumentException(string paramName, string message)
35+
{
36+
throw new ArgumentException(paramName, message);
37+
}
38+
3239
[DoesNotReturn]
3340
[MethodImpl(MethodImplOptions.NoInlining)]
3441
public static void ThrowParseException<T>(string message)
@@ -50,4 +57,4 @@ public static void ThrowMaximumStatementsException()
5057
throw new InvalidOperationException("The maximum number of statements has been reached. Your script took too long to run.");
5158
}
5259
}
53-
}
60+
}

0 commit comments

Comments
 (0)