Skip to content

Instantly share code, notes, and snippets.

@ogxd
Last active July 11, 2023 20:57
Show Gist options
  • Save ogxd/3a539be103e43e114f0ca82542a27394 to your computer and use it in GitHub Desktop.
Save ogxd/3a539be103e43e114f0ca82542a27394 to your computer and use it in GitHub Desktop.
Allocation-free string splitting. Please leave a ⭐️ if you liked it!
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";
}
}
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