Method | Dangerous | UseUtf8 | Mean | Error | StdDev |
---|---|---|---|---|---|
GetString | False | False | 21.06 ns | 0.463 ns | 0.433 ns |
StringCreate | False | False | 19.47 ns | 0.433 ns | 0.362 ns |
StringCreate_ReplaceDangerous | False | False | 28.47 ns | 0.603 ns | 0.865 ns |
StringCreate_ReplaceDangerous_IfAny | False | False | 25.24 ns | 0.484 ns | 0.452 ns |
GetString | False | True | 29.44 ns | 0.638 ns | 0.682 ns |
StringCreate | False | True | 29.16 ns | 0.504 ns | 0.421 ns |
StringCreate_ReplaceDangerous | False | True | 38.52 ns | 0.783 ns | 0.654 ns |
StringCreate_ReplaceDangerous_IfAny | False | True | 33.03 ns | 0.590 ns | 0.523 ns |
GetString | True | False | 20.33 ns | 0.311 ns | 0.276 ns |
StringCreate | True | False | 19.06 ns | 0.269 ns | 0.239 ns |
StringCreate_ReplaceDangerous | True | False | 75.23 ns | 1.555 ns | 2.022 ns |
StringCreate_ReplaceDangerous_IfAny | True | False | 77.28 ns | 1.552 ns | 1.787 ns |
GetString | True | True | 29.27 ns | 0.612 ns | 0.573 ns |
StringCreate | True | True | 27.59 ns | 0.594 ns | 0.993 ns |
StringCreate_ReplaceDangerous | True | True | 81.58 ns | 1.627 ns | 1.670 ns |
StringCreate_ReplaceDangerous_IfAny | True | True | 84.19 ns | 1.720 ns | 1.980 ns |
Last active
July 3, 2025 20:59
-
-
Save antonfirsov/b705943e8278d9207e019cc5bc94a47a to your computer and use it in GitHub Desktop.
Replace Dangerous Characters Benchmark
This file contains hidden or 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.Running; | |
using System.Buffers; | |
using System.Text; | |
BenchmarkRunner.Run<HeaderDecoder>(); | |
public class HeaderDecoder | |
{ | |
private static readonly SearchValues<char> s_dangerousCharacters = SearchValues.Create('\0', '\r', '\n'); | |
private static readonly SearchValues<byte> s_dangerousCharacterBytes = SearchValues.Create((byte)'\0', (byte)'\r', (byte)'\n'); | |
private static readonly string NoSpecialCharsTest = "qwertzuiopö ASDFGHJKLü yxcvbnmé 123456789 qwertzuiop ASDFGHJKL yxcvbnm 123456789 qwertzuiop ASDFGHJKL yxcvbnm 123456789"; | |
private static readonly string HasSpecialCharsTest = "qwertzuiopö\0ASDFGHJKLü\nyxcvbnmé\r123456789 qwertzuiop\0ASDFGHJKL\nyxcvbnm\r123456789 qwertzuiop\0ASDFGHJKL\nyxcvbnm\r123456789"; | |
[Params(true, false)] | |
public bool Dangerous { get; set; } | |
[Params(true, false)] | |
public bool UseUtf8 { get; set; } | |
private Encoding _encoding; | |
private byte[] _input; | |
[GlobalSetup] | |
public void Setup() | |
{ | |
string str = Dangerous ? HasSpecialCharsTest : NoSpecialCharsTest; | |
_input = Encoding.ASCII.GetBytes(str); | |
_encoding = UseUtf8 ? Encoding.UTF8 : Encoding.Latin1; | |
} | |
[Benchmark] | |
public string GetString() | |
{ | |
ReadOnlySpan<byte> input = _input; | |
return _encoding.GetString(input); | |
} | |
[Benchmark] | |
public string StringCreate() | |
{ | |
ReadOnlySpan<byte> input = _input; | |
int length = _encoding.GetCharCount(input); | |
return string.Create(length, new CreateHeaderState(input, _encoding), static (chars, s) => | |
{ | |
s.Encoding.GetChars(s.Bytes, chars); | |
}); | |
} | |
[Benchmark] | |
public string StringCreate_ReplaceDangerous() | |
{ | |
ReadOnlySpan<byte> input = _input; | |
int length = _encoding.GetCharCount(input); | |
return string.Create(length, new CreateHeaderState(input, _encoding), static (chars, s) => | |
{ | |
s.Encoding.GetChars(s.Bytes, chars); | |
chars.ReplaceAny(s_dangerousCharacters, ' '); | |
}); | |
} | |
[Benchmark] | |
public string StringCreate_ReplaceDangerous_IfAny() | |
{ | |
ReadOnlySpan<byte> input = _input; | |
bool replaceDangerous = input.IndexOfAny(s_dangerousCharacterBytes) >= 0; | |
int length = _encoding.GetCharCount(input); | |
return string.Create(length, new CreateHeaderState(input, _encoding, replaceDangerous), static (chars, s) => | |
{ | |
s.Encoding.GetChars(s.Bytes, chars); | |
if (s.ReplaceDangerous) chars.ReplaceAny(s_dangerousCharacters, ' '); | |
}); | |
} | |
private readonly ref struct CreateHeaderState | |
{ | |
public readonly ReadOnlySpan<byte> Bytes; | |
public readonly Encoding Encoding; | |
public readonly bool ReplaceDangerous; | |
public CreateHeaderState(ReadOnlySpan<byte> bytes, Encoding encoding, bool replaceDangerous = false) | |
{ | |
Bytes = bytes; | |
Encoding = encoding; | |
ReplaceDangerous = replaceDangerous; | |
} | |
} | |
} | |
public static class Extensions | |
{ | |
public static void ReplaceAny<T>(this Span<T> span, SearchValues<T> values, T newValue) where T : IEquatable<T>? | |
{ | |
int pos; | |
while ((pos = span.IndexOfAny(values)) >= 0) | |
{ | |
span[pos] = newValue; | |
span = span.Slice(pos + 1); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment