Skip to content

Commit 50b56b4

Browse files
committed
perf: remove allocation
remove allocations caused by boxing interface Refs: #9
1 parent 7dafb72 commit 50b56b4

5 files changed

Lines changed: 111 additions & 71 deletions

File tree

README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,17 +3,17 @@
33

44
## install
55
```sh
6-
dotnet add package GihanSoft.String.NaturalComparer --version 3.3.0
6+
dotnet add package GihanSoft.String.NaturalComparer --version 3.4.0
77
```
88
or
99
```pwsh
10-
Install-Package GihanSoft.String.NaturalComparer -Version 3.3.0
10+
Install-Package GihanSoft.String.NaturalComparer -Version 3.4.0
1111
```
1212
or
1313
```xml
1414
<!-- add this to .csproj -->
1515
<ItemGroup>
16-
<PackageReference Include="GihanSoft.String.NaturalComparer" Version="3.3.0" />
16+
<PackageReference Include="GihanSoft.String.NaturalComparer" Version="3.4.0" />
1717
</ItemGroup>
1818
```
1919

src/NaturalStringComparer/NaturalComparer.cs

Lines changed: 65 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ namespace GihanSoft.String;
1414
/// <summary>
1515
/// Natural Comparer.
1616
/// </summary>
17-
public class NaturalComparer : IComparer<string?>, IComparer<ReadOnlyMemory<char>>
17+
public sealed class NaturalComparer : IComparer<string?>, IComparer<ReadOnlyMemory<char>>
1818
{
1919
private readonly StringComparison stringComparison;
2020

@@ -81,11 +81,34 @@ public static int Compare(ReadOnlySpan<char> x, ReadOnlySpan<char> y, StringComp
8181

8282
if (char.IsDigit(xCh) && char.IsDigit(yCh))
8383
{
84-
var xOut = GetNumber(x.Slice(i), out var xNum);
85-
var yOut = GetNumber(y.Slice(i), out var yNum);
84+
var xOut = GetNumber(x.Slice(i), out var xNumAsSpan);
85+
var yOut = GetNumber(y.Slice(i), out var yNumAsSpan);
86+
87+
int compareResult;
88+
89+
if (IsUlong(xNumAsSpan) && IsUlong(yNumAsSpan))
90+
{
91+
#if NETSTANDARD2_1_OR_GREATER || NET8_0_OR_GREATER
92+
var xNum = ulong.Parse(xNumAsSpan);
93+
var yNum = ulong.Parse(yNumAsSpan);
94+
#else
95+
var xNum = ulong.Parse(xNumAsSpan.ToString());
96+
var yNum = ulong.Parse(yNumAsSpan.ToString());
97+
#endif
98+
compareResult = xNum.CompareTo(yNum);
99+
}
100+
else
101+
{
102+
#if NETSTANDARD2_1_OR_GREATER || NET8_0_OR_GREATER
103+
var xNum = BigInteger.Parse(xNumAsSpan);
104+
var yNum = BigInteger.Parse(yNumAsSpan);
105+
#else
106+
var xNum = BigInteger.Parse(xNumAsSpan.ToString());
107+
var yNum = BigInteger.Parse(yNumAsSpan.ToString());
108+
#endif
109+
compareResult = xNum.CompareTo(yNum);
110+
}
86111

87-
UnifyNumberTypes(ref xNum, ref yNum);
88-
var compareResult = xNum.CompareTo(yNum);
89112
if (compareResult != 0)
90113
{
91114
return compareResult;
@@ -97,12 +120,10 @@ public static int Compare(ReadOnlySpan<char> x, ReadOnlySpan<char> y, StringComp
97120
{
98121
return y.Length < x.Length ? -1 : 1; // "033" < "33" === true
99122
}
100-
else
101-
{
102-
x = xOut;
103-
y = yOut;
104-
continue;
105-
}
123+
124+
x = xOut;
125+
y = yOut;
126+
continue;
106127
}
107128

108129
if (xCh != yCh)
@@ -114,42 +135,47 @@ public static int Compare(ReadOnlySpan<char> x, ReadOnlySpan<char> y, StringComp
114135
return x.Length.CompareTo(y.Length);
115136
}
116137

117-
private static ReadOnlySpan<char> GetNumber(ReadOnlySpan<char> span, out IComparable number)
138+
private static bool IsUlong(ReadOnlySpan<char> number)
118139
{
119-
var i = 0;
120-
while (i < span.Length && char.IsDigit(span[i]))
140+
while (number.Length > 0 && number[0] == '0')
121141
{
122-
i++;
123-
}
124-
125-
#if NETSTANDARD2_1_OR_GREATER || NET5_0_OR_GREATER
126-
var parseInput = span[..i];
127-
#else
128-
var parseInput = span.Slice(0, i).ToString();
129-
#endif
130-
131-
if (ulong.TryParse(parseInput, out var ulongResult))
132-
{
133-
number = ulongResult;
134-
}
135-
else
136-
{
137-
number = BigInteger.Parse(parseInput);
142+
number = number.Slice(1);
138143
}
139144

140-
return span.Slice(i);
145+
// 18446744073709551615
146+
return number switch {
147+
{ Length: <= 19 } => true,
148+
{ Length: > 20 } => false,
149+
['1', < '8', ..] => true,
150+
['1', '8', < '4', ..] => true,
151+
['1', '8', '4', < '4', ..] => true,
152+
['1', '8', '4', '4', < '6', ..] => true,
153+
['1', '8', '4', '4', '6', < '7', ..] => true,
154+
['1', '8', '4', '4', '6', '7', < '4', ..] => true,
155+
['1', '8', '4', '4', '6', '7', '4', < '4', ..] => true,
156+
['1', '8', '4', '4', '6', '7', '4', '4', '0', < '7', ..] => true,
157+
['1', '8', '4', '4', '6', '7', '4', '4', '0', '7', < '3', ..] => true,
158+
['1', '8', '4', '4', '6', '7', '4', '4', '0', '7', '3', < '7', ..] => true,
159+
['1', '8', '4', '4', '6', '7', '4', '4', '0', '7', '3', '7', '0', < '9', ..] => true,
160+
['1', '8', '4', '4', '6', '7', '4', '4', '0', '7', '3', '7', '0', '9', < '5', ..] => true,
161+
['1', '8', '4', '4', '6', '7', '4', '4', '0', '7', '3', '7', '0', '9', '5', < '5', ..] => true,
162+
['1', '8', '4', '4', '6', '7', '4', '4', '0', '7', '3', '7', '0', '9', '5', '5', '0', ..] => true,
163+
['1', '8', '4', '4', '6', '7', '4', '4', '0', '7', '3', '7', '0', '9', '5', '5', '1', < '6', ..] => true,
164+
['1', '8', '4', '4', '6', '7', '4', '4', '0', '7', '3', '7', '0', '9', '5', '5', '1', '6', '0', ..] => true,
165+
['1', '8', '4', '4', '6', '7', '4', '4', '0', '7', '3', '7', '0', '9', '5', '5', '1', '6', '1', <= '5'] => true,
166+
_ => false
167+
};
141168
}
142169

143-
private static void UnifyNumberTypes(ref IComparable x, ref IComparable y)
170+
private static ReadOnlySpan<char> GetNumber(ReadOnlySpan<char> span, out ReadOnlySpan<char> number)
144171
{
145-
if (x is ulong xLong && y is BigInteger)
172+
var i = 0;
173+
while (i < span.Length && char.IsDigit(span[i]))
146174
{
147-
x = new BigInteger(xLong);
175+
i++;
148176
}
149177

150-
if (x is BigInteger && y is ulong yLong)
151-
{
152-
y = new BigInteger(yLong);
153-
}
178+
number = span.Slice(0, i);
179+
return span.Slice(i);
154180
}
155-
}
181+
}

src/NaturalStringComparer/NaturalStringComparer.csproj

Lines changed: 9 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
<Project Sdk="Microsoft.NET.Sdk">
22

33
<PropertyGroup>
4-
<TargetFrameworks>netstandard1.1;netstandard2.0;netstandard2.1;net6.0;net7.0</TargetFrameworks>
4+
<TargetFrameworks>netstandard1.1;netstandard2.0;netstandard2.1;net8.0</TargetFrameworks>
55
<LangVersion>latest</LangVersion>
66
<Nullable>enable</Nullable>
77

8-
<Version>3.3.0</Version>
8+
<Version>3.4.0</Version>
99
<RootNamespace>GihanSoft.String</RootNamespace>
1010
<Authors>Muhammad Babayi</Authors>
1111
<Company>GihanSoft</Company>
@@ -17,18 +17,8 @@
1717
<PackageRequireLicenseAcceptance>true</PackageRequireLicenseAcceptance>
1818
<PackageId>GihanSoft.String.NaturalComparer</PackageId>
1919
<PackageReleaseNotes>
20-
3.0.0:
21-
Using Span to increase speed.
22-
back to safe zone. using spans
23-
breaking changes:
24-
- remove static comparers
25-
- remove support of .net core 2.1
26-
3.1.0:
27-
fix throw if larger than Int32, now support numbers up to infinity, or your ram capacity
28-
3.2.0:
29-
optimize
30-
3.3.0:
31-
fix bug of big and noramal number
20+
3.4.0:
21+
- enhanced by removing allocations
3222
</PackageReleaseNotes>
3323
<PackageTags>string, comparer, IComparer, NaturalComparer, NaturalStringComparer, Natural-Sort, NaturalSort, String-Comparison, StringComparer, Sorting, Sort, Natural, csharp, c-sharp, C#, </PackageTags>
3424
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
@@ -41,8 +31,12 @@
4131
<PackageLicenseExpression>MIT</PackageLicenseExpression>
4232
</PropertyGroup>
4333

44-
<ItemGroup Condition="'$(TargetFramework)' == 'netstandard1.1' or '$(TargetFramework)' == 'netstandard2.0' or '$(TargetFramework)' == 'net461'">
34+
<ItemGroup Condition="'$(TargetFramework)' != 'net8.0'">
4535
<PackageReference Include="System.Memory" Version="4.5.5" />
36+
<PackageReference Include="PolySharp" Version="1.14.1">
37+
<PrivateAssets>all</PrivateAssets>
38+
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
39+
</PackageReference>
4640
</ItemGroup>
4741

4842
<ItemGroup>
Lines changed: 13 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,19 @@
11
<Project Sdk="Microsoft.NET.Sdk">
22

3-
<PropertyGroup>
4-
<TargetFramework>net7.0</TargetFramework>
3+
<PropertyGroup>
4+
<TargetFramework>net8.0</TargetFramework>
5+
<IsPackable>false</IsPackable>
6+
</PropertyGroup>
57

6-
<IsPackable>false</IsPackable>
7-
</PropertyGroup>
8+
<ItemGroup>
9+
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="*" />
10+
<PackageReference Include="xunit" Version="*" />
11+
<PackageReference Include="xunit.runner.visualstudio" Version="*" />
12+
<DotNetCliToolReference Include="dotnet-xunit" Version="*" />
13+
</ItemGroup>
814

9-
<ItemGroup>
10-
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="*" />
11-
<PackageReference Include="xunit" Version="*" />
12-
<PackageReference Include="xunit.runner.visualstudio" Version="*" />
13-
<DotNetCliToolReference Include="dotnet-xunit" Version="*" />
14-
</ItemGroup>
15-
16-
<ItemGroup>
17-
<ProjectReference Include="..\..\src\NaturalStringComparer\NaturalStringComparer.csproj" />
18-
</ItemGroup>
15+
<ItemGroup>
16+
<ProjectReference Include="..\..\src\NaturalStringComparer\NaturalStringComparer.csproj" />
17+
</ItemGroup>
1918

2019
</Project>

test/NaturalStringComparerTest/UnitTestUnsafe.cs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ public class UnitTestUnsafe
1414
private const int n = 1000000;
1515
private readonly int[] nums = new int[n];
1616

17+
private readonly NaturalComparer _sut_Ordinal = new(StringComparison.Ordinal);
18+
1719
public UnitTestUnsafe()
1820
{
1921
for (var i = 0; i < nums.Length; i++)
@@ -213,5 +215,24 @@ void TT()
213215
number9
214216
*/
215217
}
218+
219+
public static TheoryData<string, string, int> StringCompareTestData { get; } = new()
220+
{
221+
{ null, null, 0 },
222+
{ null, "z", -1 },
223+
{ "a", null, 1 },
224+
{ "val1", "val2", -1 },
225+
{ "val2", "val2", 0 },
226+
{ $"val{ulong.MaxValue}", $"val{ulong.MaxValue}", 0 },
227+
{ $"val{new BigInteger(ulong.MaxValue) + 1}", $"val{new BigInteger(ulong.MaxValue) + 1}", 0 },
228+
};
229+
230+
[Theory]
231+
[MemberData(nameof(StringCompareTestData))]
232+
public void StringCompareTest(string input1, string input2, int expected)
233+
{
234+
var actual = _sut_Ordinal.Compare(input1, input2);
235+
Assert.Equal(expected, actual);
236+
}
216237
}
217238
}

0 commit comments

Comments
 (0)