BenchmarkDotNet v0.14.0, Windows 11 (10.0.22631.4037/23H2/2023Update/SunValley3)
12th Gen Intel Core i7-1255U, 1 CPU, 12 logical and 10 physical cores
.NET SDK 9.0.100-preview.7.24407.12
[Host] : .NET 9.0.0 (9.0.24.40507), X64 RyuJIT AVX2
DefaultJob : .NET 9.0.0 (9.0.24.40507), X64 RyuJIT AVX2
| Method | Value | Mean | Error | StdDev | Code Size | Allocated |
|----------- |------ |----------:|----------:|----------:|----------:|----------:|
| TestUnsafe | 6 | 0.1193 ns | 0.0160 ns | 0.0133 ns | 215 B | - |
| TestSpan | 6 | 0.1114 ns | 0.0105 ns | 0.0093 ns | 186 B | - |
| TestRef | 6 | 0.1130 ns | 0.0133 ns | 0.0125 ns | 186 B | - |
| TestUnsafe | 17 | 0.3625 ns | 0.0168 ns | 0.0149 ns | 212 B | - |
| TestSpan | 17 | 0.1063 ns | 0.0090 ns | 0.0075 ns | 183 B | - |
| TestRef | 17 | 0.1078 ns | 0.0091 ns | 0.0081 ns | 183 B | - |
| TestUnsafe | 123 | 0.6305 ns | 0.0146 ns | 0.0137 ns | 218 B | - |
| TestSpan | 123 | 0.3738 ns | 0.0116 ns | 0.0103 ns | 181 B | - |
| TestRef | 123 | 0.3548 ns | 0.0087 ns | 0.0077 ns | 181 B | - |
Created
August 20, 2024 20:02
-
-
Save ladeak/b410b361d45549c64548912e8971da24 to your computer and use it in GitHub Desktop.
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.Runtime.CompilerServices; | |
using System.Runtime.InteropServices; | |
using BenchmarkDotNet.Attributes; | |
using BenchmarkDotNet.Running; | |
BenchmarkRunner.Run<Benchmarks>(); | |
[SimpleJob, MemoryDiagnoser, DisassemblyDiagnoser] | |
public class Benchmarks | |
{ | |
private byte[] _buffer = new byte[32]; | |
[Params(6, 17, 123)] | |
public ulong Value { get; set; } | |
[Benchmark] | |
public void TestUnsafe() | |
{ | |
WriteNumericUnsafe(_buffer, Value); | |
} | |
[Benchmark] | |
public void TestSpan() | |
{ | |
WriteNumericSpan(_buffer, Value); | |
} | |
[Benchmark] | |
public void TestRef() | |
{ | |
WriteNumericRef(_buffer, Value); | |
} | |
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |
private static unsafe void WriteNumericUnsafe(Span<byte> buffer, ulong number) | |
{ | |
const byte AsciiDigitStart = (byte)'0'; | |
var span = buffer; | |
int position = 0; | |
var bytesLeftInBlock = span.Length; | |
// Fast path, try copying to the available memory directly | |
fixed (byte* output = span) | |
{ | |
var start = output; | |
if (number < 10 && bytesLeftInBlock >= 1) | |
{ | |
*(start) = (byte)(((uint)number) + AsciiDigitStart); | |
position++; | |
} | |
else if (number < 100 && bytesLeftInBlock >= 2) | |
{ | |
var val = (uint)number; | |
var tens = (byte)((val * 205u) >> 11); // div10, valid to 1028 | |
*(start) = (byte)(tens + AsciiDigitStart); | |
*(start + 1) = (byte)(val - (tens * 10) + AsciiDigitStart); | |
position += 2; | |
} | |
else if (number < 1000 && bytesLeftInBlock >= 3) | |
{ | |
var val = (uint)number; | |
var digit0 = (byte)((val * 41u) >> 12); // div100, valid to 1098 | |
var digits01 = (byte)((val * 205u) >> 11); // div10, valid to 1028 | |
*(start) = (byte)(digit0 + AsciiDigitStart); | |
*(start + 1) = (byte)(digits01 - (digit0 * 10) + AsciiDigitStart); | |
*(start + 2) = (byte)(val - (digits01 * 10) + AsciiDigitStart); | |
position += 3; | |
} | |
} | |
} | |
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |
private static unsafe void WriteNumericSpan(Span<byte> buffer, ulong number) | |
{ | |
const byte AsciiDigitStart = (byte)'0'; | |
var span = buffer; | |
int position = 0; | |
var bytesLeftInBlock = span.Length; | |
// Fast path, try copying to the available memory directly | |
var start = span; | |
if (number < 10 && bytesLeftInBlock >= 1) | |
{ | |
start[0] = (byte)(((uint)number) + AsciiDigitStart); | |
position++; | |
} | |
else if (number < 100 && bytesLeftInBlock >= 2) | |
{ | |
var val = (uint)number; | |
var tens = (byte)((val * 205u) >> 11); // div10, valid to 1028 | |
start[0] = (byte)(tens + AsciiDigitStart); | |
start[1] = (byte)(val - (tens * 10) + AsciiDigitStart); | |
position += 2; | |
} | |
else if (number < 1000 && bytesLeftInBlock >= 3) | |
{ | |
var val = (uint)number; | |
var digit0 = (byte)((val * 41u) >> 12); // div100, valid to 1098 | |
var digits01 = (byte)((val * 205u) >> 11); // div10, valid to 1028 | |
start[0] = (byte)(digit0 + AsciiDigitStart); | |
start[1] = (byte)(digits01 - (digit0 * 10) + AsciiDigitStart); | |
start[2] = (byte)(val - (digits01 * 10) + AsciiDigitStart); | |
position += 3; | |
} | |
} | |
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |
private static unsafe void WriteNumericRef(Span<byte> buffer, ulong number) | |
{ | |
const byte AsciiDigitStart = (byte)'0'; | |
var span = buffer; | |
int position = 0; | |
var bytesLeftInBlock = span.Length; | |
// Fast path, try copying to the available memory directly | |
ref byte start = ref MemoryMarshal.GetReference(span); | |
if (number < 10 && bytesLeftInBlock >= 1) | |
{ | |
start = (byte)(((uint)number) + AsciiDigitStart); | |
position++; | |
} | |
else if (number < 100 && bytesLeftInBlock >= 2) | |
{ | |
var val = (uint)number; | |
var tens = (byte)((val * 205u) >> 11); // div10, valid to 1028 | |
start = (byte)(tens + AsciiDigitStart); | |
Unsafe.AddByteOffset(ref start, 1) = (byte)(val - (tens * 10) + AsciiDigitStart); | |
position += 2; | |
} | |
else if (number < 1000 && bytesLeftInBlock >= 3) | |
{ | |
var val = (uint)number; | |
var digit0 = (byte)((val * 41u) >> 12); // div100, valid to 1098 | |
var digits01 = (byte)((val * 205u) >> 11); // div10, valid to 1028 | |
start = (byte)(digit0 + AsciiDigitStart); | |
Unsafe.AddByteOffset(ref start, 1) = (byte)(digits01 - (digit0 * 10) + AsciiDigitStart); | |
Unsafe.AddByteOffset(ref start, 2) = (byte)(val - (digits01 * 10) + AsciiDigitStart); | |
position += 3; | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment