Last active
July 11, 2023 20:57
-
-
Save ogxd/3a539be103e43e114f0ca82542a27394 to your computer and use it in GitHub Desktop.
Allocation-free string splitting. Please leave a ⭐️ if you liked it!
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using BenchmarkDotNet.Attributes; | |
using BenchmarkDotNet.Order; | |
// | Method | str | Mean | Error | StdDev | Allocated | | |
// |-------------------- |-------------------- |-----------:|----------:|----------:|----------:| | |
// | Split | this(...)oose [131] | 162.522 ns | 1.7171 ns | 1.6062 ns | 760 B | | |
// | SplitAllocationFree | this(...)oose [131] | 1.748 ns | 0.0187 ns | 0.0175 ns | - | 🚀 | |
[Orderer(SummaryOrderPolicy.Declared)] | |
[MemoryDiagnoser(false)] | |
[SimpleJob] | |
public class SplitBenchmark | |
{ | |
[Benchmark] | |
[ArgumentsSource(nameof(StringToSplit))] | |
public string[] Split(string str) | |
{ | |
return str.Split('-'); | |
} | |
[Benchmark] | |
[ArgumentsSource(nameof(StringToSplit))] | |
public SplitEnumerator SplitAllocationFree(string str) | |
{ | |
return str.SplitAllocationFree('-'); | |
} | |
public static IEnumerable<string> StringToSplit() | |
{ | |
yield return "this is-a rather long-string-that we are-willing-to-split-but-it may take-quite some time-to-do-depending on-the solution-we choose"; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
public static class SplittingExtensions | |
{ | |
/// <summary> | |
/// Splits a string without allocations | |
/// </summary> | |
/// <param name="str"></param> | |
/// <param name="separator"></param> | |
/// <returns></returns> | |
public static SplitEnumerator SplitAllocationFree(this string str, char separator) | |
{ | |
return new SplitEnumerator(str.AsSpan(), separator); | |
} | |
/// <summary> | |
/// Splits a string without allocations | |
/// </summary> | |
/// <param name="str"></param> | |
/// <param name="separator"></param> | |
/// <returns></returns> | |
public static SplitEnumerator SplitAllocationFree(this ReadOnlySpan<char> str, char separator) | |
{ | |
return new SplitEnumerator(str, separator); | |
} | |
} | |
public ref struct SplitEnumerator | |
{ | |
private readonly ReadOnlySpan<char> _span; | |
private readonly char _splitChar; | |
private int _currentIndex; | |
public SplitEnumerator(ReadOnlySpan<char> span, char splitChar) | |
{ | |
_span = span; | |
_splitChar = splitChar; | |
_currentIndex = 0; | |
Current = ReadOnlySpan<char>.Empty; | |
} | |
public SplitEnumerator GetEnumerator() => this; | |
public ReadOnlySpan<char> Current { get; private set; } | |
internal void Reset() | |
{ | |
_currentIndex = 0; | |
Current = ReadOnlySpan<char>.Empty; | |
} | |
public bool MoveNext() | |
{ | |
if (_span.Length == 0) | |
{ | |
return false; | |
} | |
if (_currentIndex == -1) | |
{ | |
return false; | |
} | |
int index = -1; | |
for (int i = _currentIndex; i < _span.Length; i++) | |
{ | |
if (_span[i] == _splitChar) | |
{ | |
index = i; | |
break; | |
} | |
} | |
if (index == -1) | |
{ | |
Current = _span[_currentIndex..]; | |
_currentIndex = -1; | |
return true; | |
} | |
else | |
{ | |
Current = _span[_currentIndex..index]; | |
_currentIndex = index + 1; | |
} | |
return true; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment