Skip to content

Commit 4756289

Browse files
committed
Improves expression parsing efficiency.
Reduces memory allocations during expression evaluation by renaming and repurposing the pre-parsed cache to a more general placeholder cache. This allows caching of intermediate values and avoids redundant calculations. Additionally, the ExpressionState class now pre-allocates the StringBuilder for better performance.
1 parent a5de6ff commit 4756289

File tree

4 files changed

+56
-55
lines changed

4 files changed

+56
-55
lines changed

NTDLS.ExpressionParser/Expression.cs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,8 +57,6 @@ public Expression(string text, ExpressionOptions? options = null)
5757
}
5858
}
5959

60-
61-
6260
#endregion
6361

6462
#region Evaluate.

NTDLS.ExpressionParser/ExpressionState.cs

Lines changed: 31 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -10,26 +10,27 @@ namespace NTDLS.ExpressionParser
1010
internal class ExpressionState
1111
{
1212
public string WorkingText { get; set; } = string.Empty;
13-
public readonly StringBuilder Buffer = new();
13+
public readonly StringBuilder Buffer;
1414

15-
private int _nextPreParsedCacheSlot = 0;
15+
private int _nextPlaceholderCacheSlot = 0;
1616
private PreComputedCacheItem[] _preComputedCache = [];
1717
private int _nextPreComputedCacheSlot = 0;
1818
private int _operationCount = 0;
19-
private PreParsedCacheItem?[] _preParsedCache = [];
19+
private PlaceholderCacheItem?[] _placeholderCache = [];
2020
private readonly ExpressionOptions _options;
21-
private bool _isPreParsedCacheHydrated = false;
21+
private bool _isPlaceholderCacheHydrated = false;
2222

2323
public ExpressionState(Sanitized sanitized, ExpressionOptions options)
2424
{
2525
_options = options;
26+
Buffer = new StringBuilder();
2627

2728
WorkingText = sanitized.Text;
2829
_operationCount = sanitized.OperationCount;
2930
_nextPreComputedCacheSlot = sanitized.ConsumedPreComputedCacheSlots;
3031
_preComputedCache = new PreComputedCacheItem[sanitized.OperationCount];
31-
_preParsedCache = new PreParsedCacheItem?[_operationCount];
32-
_nextPreParsedCacheSlot = 0;
32+
_placeholderCache = new PlaceholderCacheItem?[_operationCount];
33+
_nextPlaceholderCacheSlot = 0;
3334

3435
for (int i = 0; i < sanitized.ConsumedPreComputedCacheSlots; i++)
3536
{
@@ -42,24 +43,26 @@ public ExpressionState(Sanitized sanitized, ExpressionOptions options)
4243
}
4344
}
4445

45-
public ExpressionState(ExpressionOptions options)
46+
public ExpressionState(ExpressionOptions options, int preAllocation)
4647
{
48+
Buffer = new StringBuilder(preAllocation);
49+
4750
_options = options;
4851
}
4952

5053
#region Pre-Parsed Cache Management.
5154

52-
public int ConsumeNextPreParsedCacheSlot()
55+
public int ConsumeNextPlaceholderCacheSlot()
5356
{
54-
return _nextPreParsedCacheSlot++;
57+
return _nextPlaceholderCacheSlot++;
5558
}
5659

5760
[MethodImpl(MethodImplOptions.AggressiveInlining)]
58-
public bool TryGetPreParsedCache(int slot, [NotNullWhen(true)] out PreParsedCacheItem value)
61+
public bool TryGetPlaceholderCache(int slot, [NotNullWhen(true)] out PlaceholderCacheItem value)
5962
{
60-
if (slot < _preParsedCache.Length)
63+
if (slot < _placeholderCache.Length)
6164
{
62-
var cached = _preParsedCache[slot];
65+
var cached = _placeholderCache[slot];
6366
if (cached != null)
6467
{
6568
value = cached.Value;
@@ -71,13 +74,13 @@ public bool TryGetPreParsedCache(int slot, [NotNullWhen(true)] out PreParsedCach
7174
}
7275

7376
[MethodImpl(MethodImplOptions.AggressiveInlining)]
74-
public void StorePreParsedCache(int slot, PreParsedCacheItem value)
77+
public void StorePlaceholderCache(int slot, PlaceholderCacheItem value)
7578
{
76-
if (slot >= _preParsedCache.Length) //Resize the cache if needed.
79+
if (slot >= _placeholderCache.Length) //Resize the cache if needed.
7780
{
78-
Array.Resize(ref _preParsedCache, (_preParsedCache.Length + 1) * 2);
81+
Array.Resize(ref _placeholderCache, (_placeholderCache.Length + 1) * 2);
7982
}
80-
_preParsedCache[slot] = value;
83+
_placeholderCache[slot] = value;
8184
}
8285

8386
#endregion
@@ -127,48 +130,48 @@ public PreComputedCacheItem GetPreComputedCacheItem(ReadOnlySpan<char> span)
127130

128131
public void HydrateTemplateParsedCache(int expressionHash)
129132
{
130-
if (!_isPreParsedCacheHydrated)
133+
if (!_isPlaceholderCacheHydrated)
131134
{
132135
lock (this)
133136
{
134-
if (!_isPreParsedCacheHydrated)
137+
if (!_isPlaceholderCacheHydrated)
135138
{
136139
if (Utility.PersistentCaches.TryGetValue(expressionHash, out CachedState? entry) && entry != null)
137140
{
138-
entry.State.HydratePreParsedCache(_preParsedCache);
141+
entry.State.HydratePlaceholderCache(_placeholderCache);
139142
}
140-
_isPreParsedCacheHydrated = true;
143+
_isPlaceholderCacheHydrated = true;
141144
}
142145
}
143146
}
144147
}
145148

146-
private void HydratePreParsedCache(PreParsedCacheItem?[] populatedCache)
149+
private void HydratePlaceholderCache(PlaceholderCacheItem?[] populatedCache)
147150
{
148-
Interlocked.Exchange(ref _preParsedCache, populatedCache);
151+
Interlocked.Exchange(ref _placeholderCache, populatedCache);
149152
}
150153

151154
public void Reset(Sanitized sanitized)
152155
{
153156
_nextPreComputedCacheSlot = sanitized.ConsumedPreComputedCacheSlots;
154-
_nextPreParsedCacheSlot = 0;
157+
_nextPlaceholderCacheSlot = 0;
155158
}
156159

157160
public ExpressionState Clone()
158161
{
159-
var clone = new ExpressionState(_options)
162+
var clone = new ExpressionState(_options, WorkingText.Length * 2)
160163
{
161164
WorkingText = WorkingText,
162165
_operationCount = _operationCount,
163166
_nextPreComputedCacheSlot = _nextPreComputedCacheSlot,
164167
_preComputedCache = new PreComputedCacheItem[_preComputedCache.Length],
165-
_preParsedCache = new PreParsedCacheItem?[_preParsedCache.Length],
166-
_nextPreParsedCacheSlot = 0,
167-
_isPreParsedCacheHydrated = _isPreParsedCacheHydrated
168+
_placeholderCache = new PlaceholderCacheItem?[_placeholderCache.Length],
169+
_nextPlaceholderCacheSlot = 0,
170+
_isPlaceholderCacheHydrated = _isPlaceholderCacheHydrated
168171
};
169172

170173
Array.Copy(_preComputedCache, clone._preComputedCache, _preComputedCache.Length); //Copy any pre-computed NULLs.
171-
Array.Copy(_preParsedCache, clone._preParsedCache, _preParsedCache.Length);
174+
Array.Copy(_placeholderCache, clone._placeholderCache, _placeholderCache.Length);
172175

173176
return clone;
174177
}

NTDLS.ExpressionParser/SubExpression.cs

Lines changed: 24 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -156,27 +156,27 @@ internal string Compute(bool isCacheable)
156156
//Pre-first-order:
157157
while ((operatorIndex = GetFreestandingNotOperation(out _)) != -1)
158158
{
159-
var preParsedCacheSlot = _parentExpression.State.ConsumeNextPreParsedCacheSlot();
159+
var placeholderCacheSlot = _parentExpression.State.ConsumeNextPlaceholderCacheSlot();
160160

161-
if (_parentExpression.State.TryGetPreParsedCache(preParsedCacheSlot, out PreParsedCacheItem cachedObj))
161+
if (_parentExpression.State.TryGetPlaceholderCache(placeholderCacheSlot, out PlaceholderCacheItem cachedObj))
162162
{
163163
StorePreComputed(cachedObj.BeginPosition, cachedObj.EndPosition, cachedObj.ParsedValue);
164164
}
165165
else
166166
{
167-
var rightValue = GetRightValue(operatorIndex + 1, out int outParsedLength, out bool isOperCacheable);
167+
var rightValue = GetRightValue(operatorIndex + 1, out int outParsedLength, out bool isOperationCacheable);
168168
int? calculatedResult = rightValue == null ? null : (rightValue == 0) ? 1 : 0;
169169
StorePreComputed(operatorIndex, operatorIndex + outParsedLength, calculatedResult);
170170

171-
if (isCacheable && isOperCacheable)
171+
if (isCacheable && isOperationCacheable)
172172
{
173-
var parsedNumber = new PreParsedCacheItem
173+
var parsedNumber = new PlaceholderCacheItem
174174
{
175175
ParsedValue = calculatedResult,
176176
BeginPosition = operatorIndex,
177177
EndPosition = operatorIndex + outParsedLength
178178
};
179-
_parentExpression.State.StorePreParsedCache(preParsedCacheSlot, parsedNumber);
179+
_parentExpression.State.StorePlaceholderCache(placeholderCacheSlot, parsedNumber);
180180
}
181181
}
182182
}
@@ -185,30 +185,30 @@ internal string Compute(bool isCacheable)
185185
operatorIndex = GetIndexOfOperation(Utility.FirstOrderOperations, out string operation);
186186
if (operatorIndex > 0)
187187
{
188-
var preParsedCacheSlot = _parentExpression.State.ConsumeNextPreParsedCacheSlot();
188+
var placeholderCacheSlot = _parentExpression.State.ConsumeNextPlaceholderCacheSlot();
189189

190-
if (_parentExpression.State.TryGetPreParsedCache(preParsedCacheSlot, out PreParsedCacheItem cachedObj))
190+
if (_parentExpression.State.TryGetPlaceholderCache(placeholderCacheSlot, out PlaceholderCacheItem cachedObj))
191191
{
192192
StorePreComputed(cachedObj.BeginPosition, cachedObj.EndPosition, cachedObj.ParsedValue);
193193
}
194194
else
195195
{
196196
double? calculatedResult = null;
197-
if (GetLeftAndRightValues(operation, operatorIndex, out double leftValue, out double rightValue, out int beginPosition, out int endPosition, out bool isOperCacheable))
197+
if (GetLeftAndRightValues(operation, operatorIndex, out double leftValue, out double rightValue, out int beginPosition, out int endPosition, out bool isOperationCacheable))
198198
{
199199
calculatedResult = Utility.ComputePrivative(leftValue, operation, rightValue);
200200
}
201201

202202
StorePreComputed(beginPosition, endPosition, calculatedResult);
203-
if (isCacheable && isOperCacheable)
203+
if (isCacheable && isOperationCacheable)
204204
{
205-
var parsedNumber = new PreParsedCacheItem
205+
var parsedNumber = new PlaceholderCacheItem
206206
{
207207
ParsedValue = calculatedResult,
208208
BeginPosition = beginPosition,
209209
EndPosition = endPosition
210210
};
211-
_parentExpression.State.StorePreParsedCache(preParsedCacheSlot, parsedNumber);
211+
_parentExpression.State.StorePlaceholderCache(placeholderCacheSlot, parsedNumber);
212212
}
213213
}
214214

@@ -219,30 +219,30 @@ internal string Compute(bool isCacheable)
219219
operatorIndex = GetIndexOfOperation(Utility.SecondOrderOperations, out operation);
220220
if (operatorIndex > 0)
221221
{
222-
var preParsedCacheSlot = _parentExpression.State.ConsumeNextPreParsedCacheSlot();
222+
var placeholderCacheSlot = _parentExpression.State.ConsumeNextPlaceholderCacheSlot();
223223

224-
if (_parentExpression.State.TryGetPreParsedCache(preParsedCacheSlot, out PreParsedCacheItem cachedObj))
224+
if (_parentExpression.State.TryGetPlaceholderCache(placeholderCacheSlot, out PlaceholderCacheItem cachedObj))
225225
{
226226
StorePreComputed(cachedObj.BeginPosition, cachedObj.EndPosition, cachedObj.ParsedValue);
227227
}
228228
else
229229
{
230230
double? calculatedResult = null;
231-
if (GetLeftAndRightValues(operation, operatorIndex, out double leftValue, out double rightValue, out int beginPosition, out int endPosition, out bool isOperCacheable))
231+
if (GetLeftAndRightValues(operation, operatorIndex, out double leftValue, out double rightValue, out int beginPosition, out int endPosition, out bool isOperationCacheable))
232232
{
233233
calculatedResult = Utility.ComputePrivative(leftValue, operation, rightValue);
234234
}
235235

236236
StorePreComputed(beginPosition, endPosition, calculatedResult);
237-
if (isCacheable && isOperCacheable)
237+
if (isCacheable && isOperationCacheable)
238238
{
239-
var parsedNumber = new PreParsedCacheItem
239+
var parsedNumber = new PlaceholderCacheItem
240240
{
241241
ParsedValue = calculatedResult,
242242
BeginPosition = beginPosition,
243243
EndPosition = endPosition
244244
};
245-
_parentExpression.State.StorePreParsedCache(preParsedCacheSlot, parsedNumber);
245+
_parentExpression.State.StorePlaceholderCache(placeholderCacheSlot, parsedNumber);
246246
}
247247
}
248248
continue;
@@ -252,31 +252,31 @@ internal string Compute(bool isCacheable)
252252
operatorIndex = GetIndexOfOperation(Utility.ThirdOrderOperations, out operation);
253253
if (operatorIndex > 0)
254254
{
255-
var preParsedCacheSlot = _parentExpression.State.ConsumeNextPreParsedCacheSlot();
255+
var placeholderCacheSlot = _parentExpression.State.ConsumeNextPlaceholderCacheSlot();
256256

257-
if (_parentExpression.State.TryGetPreParsedCache(preParsedCacheSlot, out PreParsedCacheItem cachedObj))
257+
if (_parentExpression.State.TryGetPlaceholderCache(placeholderCacheSlot, out PlaceholderCacheItem cachedObj))
258258
{
259259
StorePreComputed(cachedObj.BeginPosition, cachedObj.EndPosition, cachedObj.ParsedValue);
260260
}
261261
else
262262
{
263263
double? calculatedResult = null;
264264

265-
if (GetLeftAndRightValues(operation, operatorIndex, out double leftValue, out double rightValue, out int beginPosition, out int endPosition, out bool isOperCacheable))
265+
if (GetLeftAndRightValues(operation, operatorIndex, out double leftValue, out double rightValue, out int beginPosition, out int endPosition, out bool isOperationCacheable))
266266
{
267267
calculatedResult = Utility.ComputePrivative(leftValue, operation, rightValue);
268268
}
269269

270270
StorePreComputed(beginPosition, endPosition, calculatedResult);
271-
if (isCacheable && isOperCacheable)
271+
if (isCacheable && isOperationCacheable)
272272
{
273-
var parsedNumber = new PreParsedCacheItem
273+
var parsedNumber = new PlaceholderCacheItem
274274
{
275275
ParsedValue = calculatedResult,
276276
BeginPosition = beginPosition,
277277
EndPosition = endPosition
278278
};
279-
_parentExpression.State.StorePreParsedCache(preParsedCacheSlot, parsedNumber);
279+
_parentExpression.State.StorePlaceholderCache(placeholderCacheSlot, parsedNumber);
280280
}
281281
}
282282
continue;

NTDLS.ExpressionParser/Types.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
/// </summary>
66
public delegate double? ExpressionFunction(double[] parameters);
77

8-
internal struct PreParsedCacheItem
8+
internal struct PlaceholderCacheItem
99
{
1010
public double? ParsedValue;
1111
public int BeginPosition;

0 commit comments

Comments
 (0)