Skip to content

Commit eed5016

Browse files
authored
Merge pull request #112 from aarondandy/perf/char-cond
Optimize looping and branching for char conditions
2 parents 47b8b1a + 7322d23 commit eed5016

8 files changed

+61
-74
lines changed

WeCantSpell.Hunspell/CharacterCondition.cs

Lines changed: 18 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -147,60 +147,38 @@ public bool Equals(CharacterCondition other) =>
147147

148148
internal string GetValuesAsText() => _characters ?? string.Empty;
149149

150-
internal bool FullyMatchesFromStart(ReadOnlySpan<char> text, out int matchLength)
150+
internal int FullyMatchesFromStart(ReadOnlySpan<char> text)
151151
{
152-
matchLength = 1;
153-
154-
if (text.Length > 0)
152+
if (_mode is ModeKind.MatchSequence)
155153
{
156-
switch (_mode)
154+
if (_characters is not null && text.StartsWith(_characters.AsSpan()))
157155
{
158-
case ModeKind.PermitChars:
159-
return Contains(text[0]);
160-
161-
case ModeKind.RestrictChars:
162-
return !Contains(text[0]);
163-
164-
case ModeKind.MatchSequence:
165-
if (_characters is { Length: > 0 } && text.StartsWith(_characters.AsSpan()))
166-
{
167-
matchLength = _characters.Length;
168-
return true;
169-
}
170-
171-
break;
156+
return _characters.Length;
172157
}
173158
}
159+
else if (text.Length > 0 && Contains(text[0]) == (_mode is ModeKind.PermitChars))
160+
{
161+
return 1;
162+
}
174163

175-
return false;
164+
return 0;
176165
}
177166

178-
internal bool FullyMatchesFromEnd(ReadOnlySpan<char> text, out int matchLength)
167+
internal int FullyMatchesFromEnd(ReadOnlySpan<char> text)
179168
{
180-
matchLength = 1;
181-
182-
if (text.Length > 0)
169+
if (_mode is ModeKind.MatchSequence)
183170
{
184-
switch (_mode)
171+
if (_characters is not null && text.EndsWith(_characters.AsSpan()))
185172
{
186-
case ModeKind.PermitChars:
187-
return Contains(text[text.Length - 1]);
188-
189-
case ModeKind.RestrictChars:
190-
return !Contains(text[text.Length - 1]);
191-
192-
case ModeKind.MatchSequence:
193-
if (_characters is { Length: > 0 } && text.EndsWith(_characters.AsSpan()))
194-
{
195-
matchLength = _characters.Length;
196-
return true;
197-
}
198-
199-
break;
173+
return _characters.Length;
200174
}
201175
}
176+
else if (text.Length > 0 && Contains(text[text.Length - 1]) == (_mode is ModeKind.PermitChars))
177+
{
178+
return 1;
179+
}
202180

203-
return false;
181+
return 0;
204182
}
205183

206184
internal bool IsOnlyPossibleMatch(ReadOnlySpan<char> text, out int matchLength)

WeCantSpell.Hunspell/CharacterConditionGroup.cs

Lines changed: 29 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -159,22 +159,27 @@ public CharacterCondition this[int index]
159159
/// <returns>True when the start of the <paramref name="text"/> is matched by the conditions.</returns>
160160
public bool IsStartingMatch(ReadOnlySpan<char> text)
161161
{
162-
if (IsEmpty)
163-
{
164-
return false;
165-
}
166-
167-
foreach (var condition in _items!)
162+
if (_items is not null)
168163
{
169-
if (!condition.FullyMatchesFromStart(text, out var matchLength))
164+
foreach (var condition in _items)
170165
{
171-
return false;
166+
var matchLength = condition.FullyMatchesFromStart(text);
167+
if (matchLength > 0)
168+
{
169+
text = text.Slice(matchLength);
170+
}
171+
else
172+
{
173+
goto exit;
174+
}
175+
172176
}
173177

174-
text = text.Slice(matchLength);
178+
return true;
175179
}
176180

177-
return true;
181+
exit:
182+
return false;
178183
}
179184

180185
/// <summary>
@@ -184,22 +189,26 @@ public bool IsStartingMatch(ReadOnlySpan<char> text)
184189
/// <returns>True when the end of the <paramref name="text"/> is matched by the conditions.</returns>
185190
public bool IsEndingMatch(ReadOnlySpan<char> text)
186191
{
187-
if (IsEmpty)
188-
{
189-
return false;
190-
}
191-
192-
for (var conditionIndex = _items!.Length - 1; conditionIndex >= 0; conditionIndex--)
192+
if (_items is not null)
193193
{
194-
if (!_items[conditionIndex].FullyMatchesFromEnd(text, out var matchLength))
194+
for (var conditionIndex = _items.Length - 1; conditionIndex >= 0; conditionIndex--)
195195
{
196-
return false;
196+
var matchLength = _items[conditionIndex].FullyMatchesFromEnd(text);
197+
if (matchLength > 0)
198+
{
199+
text = text.Slice(0, text.Length - matchLength);
200+
}
201+
else
202+
{
203+
goto exit;
204+
}
197205
}
198206

199-
text = text.Slice(0, text.Length - matchLength);
207+
return true;
200208
}
201209

202-
return true;
210+
exit:
211+
return false;
203212
}
204213

205214
public bool IsOnlyPossibleMatch(ReadOnlySpan<char> text)

artifacts/bench/results/WeCantSpell.Hunspell.Benchmarks.Suites.CheckEnUsSuite-report-github.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,8 @@ Job=Check en-US Runtime=.NET 9.0
1111
```
1212
| Method | set | words | Mean | Error | StdDev | Min | Median | Ratio |
1313
|-------------- |-------- |------------- |-----------:|---------:|---------:|-----------:|-----------:|------:|
14-
| **&#39;Check words&#39;** | **Correct** | **String[3000]** | **469.2 μs** | **2.43 μs** | **2.15 μs** | **465.2 μs** | **469.3 μs** | **1.00** |
14+
| **&#39;Check words&#39;** | **Correct** | **String[3000]** | **457.7 μs** | **2.24 μs** | **1.98 μs** | **452.0 μs** | **457.8 μs** | **1.00** |
1515
| | | | | | | | | |
16-
| **&#39;Check words&#39;** | **Mix** | **String[7000]** | **5,632.9 μs** | **46.27 μs** | **43.28 μs** | **5,578.2 μs** | **5,631.4 μs** | **1.00** |
16+
| **&#39;Check words&#39;** | **Mix** | **String[7000]** | **5,541.8 μs** | **14.93 μs** | **13.97 μs** | **5,509.3 μs** | **5,544.9 μs** | **1.00** |
1717
| | | | | | | | | |
18-
| **&#39;Check words&#39;** | **Wrong** | **String[4000]** | **4,953.4 μs** | **25.87 μs** | **24.20 μs** | **4,909.2 μs** | **4,959.7 μs** | **1.00** |
18+
| **&#39;Check words&#39;** | **Wrong** | **String[4000]** | **4,906.6 μs** | **16.65 μs** | **15.58 μs** | **4,883.4 μs** | **4,909.3 μs** | **1.00** |
Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
Method,Job,AnalyzeLaunchVariance,EvaluateOverhead,MaxAbsoluteError,MaxRelativeError,MinInvokeCount,MinIterationTime,OutlierMode,Affinity,EnvironmentVariables,Jit,LargeAddressAware,Platform,PowerPlanMode,Runtime,AllowVeryLargeObjects,Concurrent,CpuGroups,Force,HeapAffinitizeMask,HeapCount,NoAffinitize,RetainVm,Server,Arguments,BuildConfiguration,Clock,EngineFactory,NuGetReferences,Toolchain,IsMutator,InvocationCount,IterationCount,IterationTime,LaunchCount,MaxIterationCount,MaxWarmupIterationCount,MemoryRandomization,MinIterationCount,MinWarmupIterationCount,RunStrategy,UnrollFactor,WarmupCount,set,words,Mean,Error,StdDev,Min,Median,Ratio
2-
'Check words',Check en-US,False,Default,Default,Default,Default,Default,Default,1111111111111111,Empty,RyuJit,Default,X64,8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c,.NET 9.0,False,True,False,True,Default,Default,False,False,False,Default,Default,Default,Default,Default,Default,Default,Default,Default,Default,Default,Default,Default,Default,Default,Default,Default,16,Default,Correct,String[3000],469.2 μs,2.43 μs,2.15 μs,465.2 μs,469.3 μs,1.00
3-
'Check words',Check en-US,False,Default,Default,Default,Default,Default,Default,1111111111111111,Empty,RyuJit,Default,X64,8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c,.NET 9.0,False,True,False,True,Default,Default,False,False,False,Default,Default,Default,Default,Default,Default,Default,Default,Default,Default,Default,Default,Default,Default,Default,Default,Default,16,Default,Mix,String[7000],"5,632.9 μs",46.27 μs,43.28 μs,"5,578.2 μs","5,631.4 μs",1.00
4-
'Check words',Check en-US,False,Default,Default,Default,Default,Default,Default,1111111111111111,Empty,RyuJit,Default,X64,8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c,.NET 9.0,False,True,False,True,Default,Default,False,False,False,Default,Default,Default,Default,Default,Default,Default,Default,Default,Default,Default,Default,Default,Default,Default,Default,Default,16,Default,Wrong,String[4000],"4,953.4 μs",25.87 μs,24.20 μs,"4,909.2 μs","4,959.7 μs",1.00
2+
'Check words',Check en-US,False,Default,Default,Default,Default,Default,Default,1111111111111111,Empty,RyuJit,Default,X64,8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c,.NET 9.0,False,True,False,True,Default,Default,False,False,False,Default,Default,Default,Default,Default,Default,Default,Default,Default,Default,Default,Default,Default,Default,Default,Default,Default,16,Default,Correct,String[3000],457.7 μs,2.24 μs,1.98 μs,452.0 μs,457.8 μs,1.00
3+
'Check words',Check en-US,False,Default,Default,Default,Default,Default,Default,1111111111111111,Empty,RyuJit,Default,X64,8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c,.NET 9.0,False,True,False,True,Default,Default,False,False,False,Default,Default,Default,Default,Default,Default,Default,Default,Default,Default,Default,Default,Default,Default,Default,Default,Default,16,Default,Mix,String[7000],"5,541.8 μs",14.93 μs,13.97 μs,"5,509.3 μs","5,544.9 μs",1.00
4+
'Check words',Check en-US,False,Default,Default,Default,Default,Default,Default,1111111111111111,Empty,RyuJit,Default,X64,8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c,.NET 9.0,False,True,False,True,Default,Default,False,False,False,Default,Default,Default,Default,Default,Default,Default,Default,Default,Default,Default,Default,Default,Default,Default,Default,Default,16,Default,Wrong,String[4000],"4,906.6 μs",16.65 μs,15.58 μs,"4,883.4 μs","4,909.3 μs",1.00

artifacts/bench/results/WeCantSpell.Hunspell.Benchmarks.Suites.CheckEnUsSuite-report.html

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
<html lang='en'>
33
<head>
44
<meta charset='utf-8' />
5-
<title>WeCantSpell.Hunspell.Benchmarks.Suites.CheckEnUsSuite-20250911-221726</title>
5+
<title>WeCantSpell.Hunspell.Benchmarks.Suites.CheckEnUsSuite-20250911-224456</title>
66

77
<style type="text/css">
88
table { border-collapse: collapse; display: block; width: 100%; overflow: auto; }
@@ -25,9 +25,9 @@
2525
<table>
2626
<thead><tr><th>Method </th><th>set</th><th>words </th><th>Mean</th><th>Error</th><th>StdDev</th><th>Min </th><th>Median</th><th>Ratio</th>
2727
</tr>
28-
</thead><tbody><tr><td>&#39;Check words&#39;</td><td>Correct</td><td>String[3000]</td><td>469.2 &mu;s</td><td>2.43 &mu;s</td><td>2.15 &mu;s</td><td>465.2 &mu;s</td><td>469.3 &mu;s</td><td>1.00</td>
29-
</tr><tr><td>&#39;Check words&#39;</td><td>Mix</td><td>String[7000]</td><td>5,632.9 &mu;s</td><td>46.27 &mu;s</td><td>43.28 &mu;s</td><td>5,578.2 &mu;s</td><td>5,631.4 &mu;s</td><td>1.00</td>
30-
</tr><tr><td>&#39;Check words&#39;</td><td>Wrong</td><td>String[4000]</td><td>4,953.4 &mu;s</td><td>25.87 &mu;s</td><td>24.20 &mu;s</td><td>4,909.2 &mu;s</td><td>4,959.7 &mu;s</td><td>1.00</td>
28+
</thead><tbody><tr><td>&#39;Check words&#39;</td><td>Correct</td><td>String[3000]</td><td>457.7 &mu;s</td><td>2.24 &mu;s</td><td>1.98 &mu;s</td><td>452.0 &mu;s</td><td>457.8 &mu;s</td><td>1.00</td>
29+
</tr><tr><td>&#39;Check words&#39;</td><td>Mix</td><td>String[7000]</td><td>5,541.8 &mu;s</td><td>14.93 &mu;s</td><td>13.97 &mu;s</td><td>5,509.3 &mu;s</td><td>5,544.9 &mu;s</td><td>1.00</td>
30+
</tr><tr><td>&#39;Check words&#39;</td><td>Wrong</td><td>String[4000]</td><td>4,906.6 &mu;s</td><td>16.65 &mu;s</td><td>15.58 &mu;s</td><td>4,883.4 &mu;s</td><td>4,909.3 &mu;s</td><td>1.00</td>
3131
</tr></tbody></table>
3232
</body>
3333
</html>

artifacts/bench/results/WeCantSpell.Hunspell.Benchmarks.Suites.SuggestEnUsSuite-report-github.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,4 +11,4 @@ Job=Suggest en-US Runtime=.NET 9.0
1111
```
1212
| Method | Mean | Error | StdDev | Min | Median | Ratio |
1313
|---------------- |--------:|---------:|---------:|--------:|--------:|------:|
14-
| &#39;Suggest words&#39; | 1.447 s | 0.0031 s | 0.0029 s | 1.440 s | 1.447 s | 1.00 |
14+
| &#39;Suggest words&#39; | 1.455 s | 0.0040 s | 0.0038 s | 1.449 s | 1.454 s | 1.00 |
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
11
Method,Job,AnalyzeLaunchVariance,EvaluateOverhead,MaxAbsoluteError,MaxRelativeError,MinInvokeCount,MinIterationTime,OutlierMode,Affinity,EnvironmentVariables,Jit,LargeAddressAware,Platform,PowerPlanMode,Runtime,AllowVeryLargeObjects,Concurrent,CpuGroups,Force,HeapAffinitizeMask,HeapCount,NoAffinitize,RetainVm,Server,Arguments,BuildConfiguration,Clock,EngineFactory,NuGetReferences,Toolchain,IsMutator,InvocationCount,IterationCount,IterationTime,LaunchCount,MaxIterationCount,MaxWarmupIterationCount,MemoryRandomization,MinIterationCount,MinWarmupIterationCount,RunStrategy,UnrollFactor,WarmupCount,Mean,Error,StdDev,Min,Median,Ratio
2-
'Suggest words',Suggest en-US,False,Default,Default,Default,Default,Default,Default,1111111111111111,Empty,RyuJit,Default,X64,8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c,.NET 9.0,False,True,False,True,Default,Default,False,False,False,Default,Default,Default,Default,Default,Default,Default,Default,Default,Default,Default,Default,Default,Default,Default,Default,Default,16,Default,1.447 s,0.0031 s,0.0029 s,1.440 s,1.447 s,1.00
2+
'Suggest words',Suggest en-US,False,Default,Default,Default,Default,Default,Default,1111111111111111,Empty,RyuJit,Default,X64,8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c,.NET 9.0,False,True,False,True,Default,Default,False,False,False,Default,Default,Default,Default,Default,Default,Default,Default,Default,Default,Default,Default,Default,Default,Default,Default,Default,16,Default,1.455 s,0.0040 s,0.0038 s,1.449 s,1.454 s,1.00

artifacts/bench/results/WeCantSpell.Hunspell.Benchmarks.Suites.SuggestEnUsSuite-report.html

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
<html lang='en'>
33
<head>
44
<meta charset='utf-8' />
5-
<title>WeCantSpell.Hunspell.Benchmarks.Suites.SuggestEnUsSuite-20250911-221814</title>
5+
<title>WeCantSpell.Hunspell.Benchmarks.Suites.SuggestEnUsSuite-20250911-224544</title>
66

77
<style type="text/css">
88
table { border-collapse: collapse; display: block; width: 100%; overflow: auto; }
@@ -25,7 +25,7 @@
2525
<table>
2626
<thead><tr><th>Method </th><th>Mean</th><th>Error</th><th>StdDev</th><th>Min</th><th>Median</th><th>Ratio</th>
2727
</tr>
28-
</thead><tbody><tr><td>&#39;Suggest words&#39;</td><td>1.447 s</td><td>0.0031 s</td><td>0.0029 s</td><td>1.440 s</td><td>1.447 s</td><td>1.00</td>
28+
</thead><tbody><tr><td>&#39;Suggest words&#39;</td><td>1.455 s</td><td>0.0040 s</td><td>0.0038 s</td><td>1.449 s</td><td>1.454 s</td><td>1.00</td>
2929
</tr></tbody></table>
3030
</body>
3131
</html>

0 commit comments

Comments
 (0)