Last active
December 19, 2021 05:00
-
-
Save leandromoh/75c78e276ec2393497f858eef60bbb6c to your computer and use it in GitHub Desktop.
TryFormat for Enum with Span.cs
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.Running; | |
using System; | |
using System.Diagnostics; | |
using System.Globalization; | |
using System.Linq; | |
using System.Linq.Expressions; | |
// ``` ini | |
// | |
// BenchmarkDotNet=v0.13.1, OS=Windows 10.0.18363.1440 (1909/November2019Update/19H2) | |
// Intel Core i7-8650U CPU 1.90GHz (Kaby Lake R), 1 CPU, 8 logical and 4 physical cores | |
// .NET SDK=6.0.100-preview.6.21355.2 | |
// [Host] : .NET 6.0.0 (6.0.21.35212), X64 RyuJIT | |
// DefaultJob : .NET 6.0.0 (6.0.21.35212), X64 RyuJIT | |
// | |
// | |
// ``` | |
// | Method | N | Mean | Error | StdDev | Median | Gen 0 | Allocated | | |
// |------------------ |------- |-----------:|----------:|---------:|-----------:|----------:|------------:| | |
// | SPAN_Parse_text | 100000 | 976.3 μs | 36.55 μs | 106.6 μs | 926.1 μs | - | 128 B | | |
// | SPAN_Parse_number | 100000 | 1,949.5 μs | 86.76 μs | 255.8 μs | 1,913.6 μs | - | 128 B | | |
// | STR_Parse_text | 100000 | 2,942.4 μs | 99.17 μs | 290.8 μs | 2,806.8 μs | 570.3125 | 2,400,257 B | | |
// | STR_Parse_number | 100000 | 4,418.8 μs | 139.81 μs | 405.6 μs | 4,231.5 μs | 1335.9375 | 5,600,602 B | | |
namespace ConsoleApp1 | |
{ | |
public class Program | |
{ | |
public static void Main(string[] args) | |
{ | |
#if DEBUG | |
var d = new EnumParse(); | |
d.Parse_text(); | |
#else | |
var summary = BenchmarkRunner.Run(typeof(Program).Assembly); | |
#endif | |
} | |
} | |
internal enum Color | |
{ | |
Black, | |
White, | |
Yellow, | |
LightBlue, | |
} | |
[MemoryDiagnoser] | |
public class EnumParse | |
{ | |
[Params(100_000)] | |
public int N | |
#if DEBUG | |
= 1000 | |
#endif | |
; | |
[Benchmark] | |
public void Parse_text() | |
{ | |
Span<char> destination = new char[50]; | |
var charsWritten = 0; | |
for (var i = 0; i < N; i++) | |
{ | |
var isSuccess = MyEnum<Color>.TryFormat(Color.LightBlue, destination, out charsWritten); | |
Debug.Assert(isSuccess); | |
Debug.Assert(destination.Slice(0, charsWritten).SequenceEqual(nameof(Color.LightBlue))); | |
} | |
} | |
[Benchmark] | |
public void Parse_number() | |
{ | |
Span<char> destination = new char[50]; | |
var charsWritten = 0; | |
var enumValue = (Color)777; | |
for (var i = 0; i < N; i++) | |
{ | |
var isSuccess = MyEnum<Color>.TryFormat(enumValue, destination, out charsWritten); | |
Debug.Assert(isSuccess); | |
Debug.Assert(destination.Slice(0, charsWritten).SequenceEqual("777")); | |
} | |
} | |
[Benchmark] | |
public void STR_Parse_text() | |
{ | |
for (var i = 0; i < N; i++) | |
{ | |
var result = Color.LightBlue.ToString(); | |
} | |
} | |
[Benchmark] | |
public void STR_Parse_number() | |
{ | |
var enumValue = ((Color)777); | |
for (var i = 0; i < N; i++) | |
{ | |
var result = enumValue.ToString(); | |
} | |
} | |
} | |
public static class MyEnum<T> | |
{ | |
private delegate (bool isSuccess, int charsWritten) FuncSpanT(T value, Span<char> destination); | |
private static readonly FuncSpanT format; | |
// Considering type T as Color, format is equivalent to follow expression: | |
// (Color color, Span<char> span_Char) => | |
// { | |
// int charsWritten; | |
// return (color == Color.Black) | |
// ? (((uint)span_Char.Length) >= 5) | |
// ? { | |
// "Black".AsSpan().CopyTo(span_Char); | |
// | |
// return (True, 5); | |
// } | |
// : (False, 0) | |
// : (color == Color.White) | |
// ? (((uint)span_Char.Length) >= 5) | |
// ? { | |
// "White".AsSpan().CopyTo(span_Char); | |
// | |
// return (True, 5); | |
// } | |
// : (False, 0) | |
// : (color == Color.Yellow) | |
// ? (((uint)span_Char.Length) >= 6) | |
// ? { | |
// "Yellow".AsSpan().CopyTo(span_Char); | |
// | |
// return (True, 6); | |
// } | |
// : (False, 0) | |
// : (color == Color.LightBlue) | |
// ? (((uint)span_Char.Length) >= 9) | |
// ? { | |
// "LightBlue".AsSpan().CopyTo(span_Char); | |
// | |
// return (True, 9); | |
// } | |
// : (False, 0) | |
// : ((int)color).TryFormat( | |
// span_Char, | |
// out charsWritten, | |
// default(ReadOnlySpan<char>), | |
// null) | |
// ? (True, charsWritten) | |
// : (False, charsWritten); | |
// } | |
static MyEnum() | |
{ | |
var f = TryFormatEnum(); | |
format = f.Compile(); | |
} | |
public static bool TryFormat(T value, Span<char> destination, out int charsWritten) | |
{ | |
var result = format(value, destination); | |
charsWritten = result.charsWritten; | |
return result.isSuccess; | |
} | |
private static Expression<FuncSpanT> TryFormatEnum() | |
{ | |
var enumValue = Expression.Parameter(typeof(T)); | |
var type = enumValue.Type; | |
var span = Expression.Parameter(typeof(Span<char>)); | |
Debug.Assert(type.IsEnum); | |
Debug.Assert(span.Type == typeof(Span<char>)); | |
var _boolIntTupleConstructor = typeof((bool, int)).GetConstructor(new[] { typeof(bool), typeof(int) }); | |
var under = Enum.GetUnderlyingType(type); | |
var charsWritten = Expression.Variable(typeof(int), "charsWritten"); | |
var body = Enum.GetValues(type) | |
.Cast<object>() | |
.Select(color => | |
{ | |
var text = color.ToString(); | |
var valueEquals = Expression.Equal(enumValue, Expression.Constant(color, type)); | |
var enoughSpace = Expression.GreaterThanOrEqual( | |
Expression.Convert(Expression.PropertyOrField(span, "Length"), typeof(uint)), | |
Expression.Constant((uint)text.Length, typeof(uint))); | |
var strSpan = StringAsSpan(Expression.Constant(text)); | |
var ifTrue = Expression.Block( | |
Expression.Call(strSpan, "CopyTo", Type.EmptyTypes, span), | |
Expression.Constant((true, text.Length))); | |
var ifFalse = Expression.Constant((false, 0)); | |
var block = Expression.Condition(enoughSpace, ifTrue, ifFalse); | |
return (value: block, condition: valueEquals); | |
}) | |
.Reverse() | |
.Aggregate(Expression.Condition( | |
test: Expression.Call( | |
Expression.Convert(enumValue, under), "TryFormat", Type.EmptyTypes, span, charsWritten, | |
Expression.Default(typeof(ReadOnlySpan<char>)), Expression.Constant(null, typeof(CultureInfo))), | |
ifTrue: CreateTuple(true, charsWritten), | |
ifFalse: CreateTuple(false, charsWritten)), | |
(acc, item) => | |
Expression.Condition( | |
item.condition, | |
item.value, | |
acc)); | |
var blockExpr = Expression.Block( | |
variables: new[] { charsWritten }, | |
expressions: body); | |
var lambda = Expression.Lambda<FuncSpanT>(blockExpr, enumValue, span); | |
return lambda; | |
Expression StringAsSpan(Expression str) => | |
Expression.Call(typeof(MemoryExtensions), "AsSpan", Type.EmptyTypes, str); | |
NewExpression CreateTuple(bool success, Expression countWritten) | |
{ | |
Debug.Assert(countWritten.Type == typeof(int)); | |
return Expression.New(_boolIntTupleConstructor, Expression.Constant(success), countWritten); | |
} | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
coded for RecordParser project