diff --git a/README.md b/README.md index fa74f35..c822a2e 100644 --- a/README.md +++ b/README.md @@ -463,57 +463,57 @@ Job=DefaultJob | GenerateNonMono | Guid *(5) | 47.1776 ns | 0.0932 ns | - | - | | GenerateNonMono | GuidV7 *(3,5) | 77.4890 ns | 0.2267 ns | - | - | -| FromByteArray | ByteAetherUlid | 0.7978 ns | 0.0060 ns | - | - | -| FromByteArray | NetUlid | 1.4778 ns | 0.0053 ns | - | - | -| FromByteArray | Ulid | 1.1777 ns | 0.0042 ns | - | - | -| FromByteArray | NUlid | 1.1467 ns | 0.0077 ns | - | - | -| FromByteArray | Guid | 1.0420 ns | 0.0055 ns | - | - | - -| FromGuid | ByteAetherUlid | 0.8428 ns | 0.0140 ns | - | - | -| FromGuid | NetUlid | 1.7816 ns | 0.0219 ns | - | - | -| FromGuid | Ulid | 2.0271 ns | 0.0274 ns | - | - | -| FromGuid | NUlid | 1.0718 ns | 0.0353 ns | - | - | - -| FromString | ByteAetherUlid | 15.2981 ns | 0.0964 ns | - | - | -| FromString | NetUlid | 28.0353 ns | 0.3071 ns | - | - | -| FromString | Ulid | 17.4969 ns | 0.0463 ns | - | - | -| FromString | NUlid | 55.3277 ns | 0.1974 ns | 0.0086 | 72 B | -| FromString | Guid | 22.0493 ns | 0.1599 ns | - | - | - -| ToByteArray | ByteAetherUlid | 4.6643 ns | 0.0933 ns | 0.0048 | 40 B | -| ToByteArray | AsByteSpan *(6) | 0.7740 ns | 0.0041 ns | - | - | -| ToByteArray | NetUlid | 9.5123 ns | 0.1098 ns | 0.0048 | 40 B | -| ToByteArray | Ulid | 4.6310 ns | 0.0918 ns | 0.0048 | 40 B | -| ToByteArray | NUlid | 8.6575 ns | 0.1394 ns | 0.0048 | 40 B | - -| ToGuid | ByteAetherUlid | 0.8021 ns | 0.0073 ns | - | - | -| ToGuid | NetUlid | 10.2952 ns | 0.0195 ns | - | - | -| ToGuid | Ulid | 1.2186 ns | 0.0057 ns | - | - | -| ToGuid | NUlid | 0.7696 ns | 0.0039 ns | - | - | - -| ToString | ByteAetherUlid | 21.584 ns | 0.2609 ns | 0.0095 | 80 B | -| ToString | NetUlid | 27.315 ns | 0.3311 ns | 0.0095 | 80 B | -| ToString | Ulid | 23.141 ns | 0.3722 ns | 0.0095 | 80 B | -| ToString | NUlid | 27.861 ns | 0.2217 ns | 0.0095 | 80 B | -| ToString | Guid | 9.113 ns | 0.1121 ns | 0.0115 | 96 B | - -| CompareTo | ByteAetherUlid | 1.2997 ns | 0.0105 ns | - | - | -| CompareTo | NetUlid | 4.6796 ns | 0.0222 ns | - | - | -| CompareTo | Ulid | 6.7590 ns | 0.0167 ns | - | - | -| CompareTo | NUlid | 9.0615 ns | 0.0564 ns | - | - | -| CompareTo | Guid | 4.7613 ns | 0.0244 ns | - | - | - -| Equals | ByteAetherUlid | 1.0668 ns | 0.0065 ns | - | - | -| Equals | NetUlid | 1.9436 ns | 0.0050 ns | - | - | -| Equals | Ulid | 1.0576 ns | 0.0055 ns | - | - | -| Equals | NUlid | 1.0485 ns | 0.0040 ns | - | - | -| Equals | Guid | 1.1008 ns | 0.0073 ns | - | - | - -| GetHashCode | ByteAetherUlid | 0.9074 ns | 0.0057 ns | - | - | -| GetHashCode | NetUlid | 8.8425 ns | 0.0366 ns | - | - | -| GetHashCode | Ulid | 0.9066 ns | 0.0051 ns | - | - | -| GetHashCode | NUlid | 6.9351 ns | 0.0229 ns | - | - | -| GetHashCode | Guid | 0.9417 ns | 0.0072 ns | - | - | +| FromByteArray | ByteAetherUlid | 0.8044 ns | 0.0070 ns | - | - | +| FromByteArray | NetUlid | 1.4115 ns | 0.0111 ns | - | - | +| FromByteArray | Ulid | 1.1808 ns | 0.0080 ns | - | - | +| FromByteArray | NUlid | 1.1515 ns | 0.0063 ns | - | - | +| FromByteArray | Guid | 1.0440 ns | 0.0059 ns | - | - | + +| FromGuid | ByteAetherUlid | 0.8255 ns | 0.0057 ns | - | - | +| FromGuid | NetUlid | 2.0028 ns | 0.0373 ns | - | - | +| FromGuid | Ulid | 1.9495 ns | 0.0116 ns | - | - | +| FromGuid | NUlid | 0.9240 ns | 0.0183 ns | - | - | + +| FromString | ByteAetherUlid | 14.4357 ns | 0.0392 ns | - | - | +| FromString | NetUlid | 27.2159 ns | 0.0690 ns | - | - | +| FromString | Ulid | 16.9972 ns | 0.0311 ns | - | - | +| FromString | NUlid | 53.9897 ns | 0.1649 ns | 0.0086 | 72 B | +| FromString | Guid | 21.8639 ns | 0.1228 ns | - | - | + +| ToByteArray | ByteAetherUlid | 4.7470 ns | 0.1274 ns | 0.0048 | 40 B | +| ToByteArray | AsByteSpan *(6) | 0.7736 ns | 0.0051 ns | - | - | +| ToByteArray | NetUlid | 9.4871 ns | 0.1054 ns | 0.0048 | 40 B | +| ToByteArray | Ulid | 4.7189 ns | 0.1066 ns | 0.0048 | 40 B | +| ToByteArray | NUlid | 8.7597 ns | 0.1460 ns | 0.0048 | 40 B | + +| ToGuid | ByteAetherUlid | 0.8170 ns | 0.0070 ns | - | - | +| ToGuid | NetUlid | 10.3155 ns | 0.0230 ns | - | - | +| ToGuid | Ulid | 0.9731 ns | 0.0074 ns | - | - | +| ToGuid | NUlid | 0.7636 ns | 0.0061 ns | - | - | + +| ToString | ByteAetherUlid | 21.5166 ns | 0.3377 ns | 0.0095 | 80 B | +| ToString | NetUlid | 27.3102 ns | 0.3004 ns | 0.0095 | 80 B | +| ToString | Ulid | 23.4614 ns | 0.2211 ns | 0.0095 | 80 B | +| ToString | NUlid | 29.4123 ns | 0.2632 ns | 0.0095 | 80 B | +| ToString | Guid | 10.2546 ns | 0.2493 ns | 0.0115 | 96 B | + +| CompareTo | ByteAetherUlid | 1.4082 ns | 0.0071 ns | - | - | +| CompareTo | NetUlid | 4.4499 ns | 0.0303 ns | - | - | +| CompareTo | Ulid | 6.6206 ns | 0.0352 ns | - | - | +| CompareTo | NUlid | 9.2860 ns | 0.0495 ns | - | - | +| CompareTo | Guid | 4.8326 ns | 0.0210 ns | - | - | + +| Equals | ByteAetherUlid | 1.0720 ns | 0.0135 ns | - | - | +| Equals | NetUlid | 1.9610 ns | 0.0155 ns | - | - | +| Equals | Ulid | 1.0432 ns | 0.0052 ns | - | - | +| Equals | NUlid | 1.0552 ns | 0.0103 ns | - | - | +| Equals | Guid | 1.1030 ns | 0.0107 ns | - | - | + +| GetHashCode | ByteAetherUlid | 0.9185 ns | 0.0053 ns | - | - | +| GetHashCode | NetUlid | 8.8601 ns | 0.0250 ns | - | - | +| GetHashCode | Ulid | 0.9362 ns | 0.0059 ns | - | - | +| GetHashCode | NUlid | 6.9396 ns | 0.0387 ns | - | - | +| GetHashCode | Guid | 0.9082 ns | 0.0049 ns | - | - | ``` Existing competitive libraries exhibit various deviations from the official ULID specification or present drawbacks: diff --git a/src/ByteAether.Ulid.Tests/Ulid.Boundaries.Tests.cs b/src/ByteAether.Ulid.Tests/Ulid.Boundaries.Tests.cs index 6d546b0..b60ff3b 100644 --- a/src/ByteAether.Ulid.Tests/Ulid.Boundaries.Tests.cs +++ b/src/ByteAether.Ulid.Tests/Ulid.Boundaries.Tests.cs @@ -23,7 +23,7 @@ public void MinValue_ShouldBeDefault() // Assert Assert.Equal(default, ulid); - Assert.Equal(emptyBytes, ulid.AsByteSpan()); + Assert.Equal(emptyBytes, ulid.ToByteArray()); } [Fact] diff --git a/src/ByteAether.Ulid/Ulid.Comparable.cs b/src/ByteAether.Ulid/Ulid.Comparable.cs index 0df0b58..9df8c95 100644 --- a/src/ByteAether.Ulid/Ulid.Comparable.cs +++ b/src/ByteAether.Ulid/Ulid.Comparable.cs @@ -17,7 +17,9 @@ namespace ByteAether.Ulid; /// The second ULID to compare. /// True if the value of the left ULID is less than the value of the right ULID; otherwise, false. #if NETCOREAPP3_0_OR_GREATER - [MethodImpl(MethodImplOptions.AggressiveOptimization)] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] +#else + [MethodImpl(MethodImplOptions.AggressiveInlining)] #endif public static bool operator <(Ulid left, Ulid right) => left.CompareTo(right) < 0; @@ -29,7 +31,9 @@ namespace ByteAether.Ulid; /// The second ULID to compare. /// True if the value of the left ULID is less than or equal to the value of the right ULID; otherwise, false. #if NETCOREAPP3_0_OR_GREATER - [MethodImpl(MethodImplOptions.AggressiveOptimization)] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] +#else + [MethodImpl(MethodImplOptions.AggressiveInlining)] #endif public static bool operator <=(Ulid left, Ulid right) => left.CompareTo(right) <= 0; @@ -41,7 +45,9 @@ namespace ByteAether.Ulid; /// The second ULID to compare. /// True if the value of the left ULID is greater than the value of the right ULID; otherwise, false. #if NETCOREAPP3_0_OR_GREATER - [MethodImpl(MethodImplOptions.AggressiveOptimization)] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] +#else + [MethodImpl(MethodImplOptions.AggressiveInlining)] #endif public static bool operator >(Ulid left, Ulid right) => left.CompareTo(right) > 0; @@ -53,14 +59,18 @@ namespace ByteAether.Ulid; /// The second ULID to compare. /// True if the value of the left ULID is greater than or equal to the value of the right ULID; otherwise, false. #if NETCOREAPP3_0_OR_GREATER - [MethodImpl(MethodImplOptions.AggressiveOptimization)] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] +#else + [MethodImpl(MethodImplOptions.AggressiveInlining)] #endif public static bool operator >=(Ulid left, Ulid right) => left.CompareTo(right) >= 0; /// #if NETCOREAPP3_0_OR_GREATER - [MethodImpl(MethodImplOptions.AggressiveOptimization)] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] +#else + [MethodImpl(MethodImplOptions.AggressiveInlining)] #endif public int CompareTo(object? obj) { @@ -79,15 +89,15 @@ public int CompareTo(object? obj) /// #if NETCOREAPP3_0_OR_GREATER - [MethodImpl(MethodImplOptions.AggressiveOptimization)] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] +#else + [MethodImpl(MethodImplOptions.AggressiveInlining)] #endif public int CompareTo(Ulid other) => CompareToCore(this, other); #if NETCOREAPP3_0_OR_GREATER - [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] -#else - [MethodImpl(MethodImplOptions.AggressiveInlining)] + [MethodImpl(MethodImplOptions.AggressiveOptimization)] #endif private static int CompareToCore(in Ulid left, in Ulid right) { diff --git a/src/ByteAether.Ulid/Ulid.Equatable.cs b/src/ByteAether.Ulid/Ulid.Equatable.cs index 1be2aee..3c9483f 100644 --- a/src/ByteAether.Ulid/Ulid.Equatable.cs +++ b/src/ByteAether.Ulid/Ulid.Equatable.cs @@ -16,9 +16,19 @@ namespace ByteAether.Ulid; #endif { /// +#if NETCOREAPP3_0_OR_GREATER + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] +#else + [MethodImpl(MethodImplOptions.AggressiveInlining)] +#endif public int GetHashCode(Ulid ulid) => ulid.GetHashCode(); /// +#if NETCOREAPP3_0_OR_GREATER + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] +#else + [MethodImpl(MethodImplOptions.AggressiveInlining)] +#endif public bool Equals(Ulid x, Ulid y) => EqualsCore(x, y); /// @@ -38,14 +48,18 @@ public override int GetHashCode() /// #if NETCOREAPP3_0_OR_GREATER - [MethodImpl(MethodImplOptions.AggressiveOptimization)] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] +#else + [MethodImpl(MethodImplOptions.AggressiveInlining)] #endif public bool Equals(Ulid other) => EqualsCore(this, other); /// #if NETCOREAPP3_0_OR_GREATER - [MethodImpl(MethodImplOptions.AggressiveOptimization)] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] +#else + [MethodImpl(MethodImplOptions.AggressiveInlining)] #endif public override bool Equals([NotNullWhen(true)] object? obj) => obj is Ulid ulid && EqualsCore(this, ulid); @@ -56,8 +70,11 @@ public override bool Equals([NotNullWhen(true)] object? obj) /// The first ULID to compare. /// The second ULID to compare. /// True if the value of the left ULID is equal to the value of the right ULID; otherwise, false. + #if NETCOREAPP3_0_OR_GREATER - [MethodImpl(MethodImplOptions.AggressiveOptimization)] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] +#else + [MethodImpl(MethodImplOptions.AggressiveInlining)] #endif public static bool operator ==(Ulid left, Ulid right) => EqualsCore(left, right); @@ -75,9 +92,7 @@ public override bool Equals([NotNullWhen(true)] object? obj) [SkipLocalsInit] #endif #if NETCOREAPP3_0_OR_GREATER - [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] -#else - [MethodImpl(MethodImplOptions.AggressiveInlining)] + [MethodImpl(MethodImplOptions.AggressiveOptimization)] #endif private static bool EqualsCore(in Ulid left, in Ulid right) { diff --git a/src/ByteAether.Ulid/Ulid.Guid.cs b/src/ByteAether.Ulid/Ulid.Guid.cs index 8e21318..028ecec 100644 --- a/src/ByteAether.Ulid/Ulid.Guid.cs +++ b/src/ByteAether.Ulid/Ulid.Guid.cs @@ -10,7 +10,7 @@ namespace ByteAether.Ulid; #if NET8_0_OR_GREATER // We need to target netstandard2.1, so keep using ref for MemoryMarshal.Write -// CS9191: The 'ref' modifier for argument 2 corresponding to 'in' parameter is equivalent to 'in'. Consider using 'in' instead. +// CS9191: The 'ref' modifier for argument 2 corresponding to the 'in' parameter is equivalent to 'in'. Consider using 'in' instead. #pragma warning disable CS9191 #endif @@ -29,14 +29,9 @@ public readonly partial struct Ulid [MethodImpl(MethodImplOptions.AggressiveInlining)] #endif public static Ulid New(Guid guid) - { - if (BitConverter.IsLittleEndian) - { - return Shuffle(ref guid); - } - - return Unsafe.As(ref guid); - } + => BitConverter.IsLittleEndian + ? Shuffle(ref guid) + : Unsafe.As(ref guid); /// /// Converts the ULID to a GUID. @@ -51,14 +46,9 @@ public static Ulid New(Guid guid) [MethodImpl(MethodImplOptions.AggressiveInlining)] #endif public Guid ToGuid() - { - if (BitConverter.IsLittleEndian) - { - return Shuffle(ref Unsafe.AsRef(in this)); - } - - return Unsafe.As(ref Unsafe.AsRef(in this)); - } + => BitConverter.IsLittleEndian + ? Shuffle(ref Unsafe.AsRef(in this)) + : Unsafe.As(ref Unsafe.AsRef(in this)); /// /// Implicitly converts a ULID to a GUID. @@ -87,12 +77,6 @@ public Guid ToGuid() #if NETCOREAPP private static readonly Vector128 _shuffleMask = Vector128.Create((byte)3, 2, 1, 0, 5, 4, 7, 6, 8, 9, 10, 11, 12, 13, 14, 15); - - private static readonly bool _isAccelerated = -#if NET7_0_OR_GREATER - Vector128.IsHardwareAccelerated || -#endif - Ssse3.IsSupported; #endif // HACK: We assume the layout of a Guid is the following: @@ -109,19 +93,17 @@ private static readonly Vector128 _shuffleMask #endif private static TOut Shuffle(ref TIn bytes) { -#if NETCOREAPP - if (_isAccelerated) +#if NET7_0_OR_GREATER + if (Vector128.IsHardwareAccelerated) + { + var vector = Unsafe.As>(ref bytes); + vector = Vector128.Shuffle(vector, _shuffleMask); + return Unsafe.As, TOut>(ref vector); + } +#elif NETCOREAPP3_0_OR_GREATER + if (Ssse3.IsSupported) { var vector = Unsafe.As>(ref bytes); - -#if NET7_0_OR_GREATER - if (Vector128.IsHardwareAccelerated) - { - vector = Vector128.Shuffle(vector, _shuffleMask); - return Unsafe.As, TOut>(ref vector); - } -#endif - vector = Ssse3.Shuffle(vector, _shuffleMask); return Unsafe.As, TOut>(ref vector); } diff --git a/src/ByteAether.Ulid/Ulid.IsValid.cs b/src/ByteAether.Ulid/Ulid.IsValid.cs index 80bba6c..449e0f0 100644 --- a/src/ByteAether.Ulid/Ulid.IsValid.cs +++ b/src/ByteAether.Ulid/Ulid.IsValid.cs @@ -1,4 +1,5 @@ using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; namespace ByteAether.Ulid; @@ -31,26 +32,81 @@ public readonly partial struct Ulid #if NETCOREAPP3_0_OR_GREATER [MethodImpl(MethodImplOptions.AggressiveOptimization)] #endif - public static bool IsValid(ReadOnlySpan ulidString) + public static unsafe bool IsValid(ReadOnlySpan ulidString) { - // Length check - if (ulidString.Length != UlidStringLength) + if (ulidString.Length != UlidStringLength) // 26 { return false; } - var firstChar = ulidString[0]; - if (_inverseBase32[firstChar] > 7) + fixed (char* src = &MemoryMarshal.GetReference(ulidString)) { - return false; - } - - for (var i = 1; i < UlidStringLength; i++) - { - if (_inverseBase32[ulidString[i]] == 255) + // 1. Fast check for the first character (prevent 128-bit overflow) + uint c0 = src[0]; + if (c0 > 255 || _inverseBase32[c0] > 7) { return false; } + + fixed (byte* table = _inverseBase32) + { + // Block A: Indices 1 to 6 + if (((src[1] | src[2] | src[3] | src[4] | src[5] | src[6]) & 0xFF00) != 0) + { + return false; + } + + if ( + table[src[1]] == 255 || table[src[2]] == 255 || table[src[3]] == 255 + || table[src[4]] == 255 || table[src[5]] == 255 || table[src[6]] == 255 + ) + { + return false; + } + + // Block B: Indices 7 to 12 + if (((src[7] | src[8] | src[9] | src[10] | src[11] | src[12]) & 0xFF00) != 0) + { + return false; + } + + if ( + table[src[7]] == 255 || table[src[8]] == 255 || table[src[9]] == 255 + || table[src[10]] == 255 || table[src[11]] == 255 || table[src[12]] == 255 + ) + { + return false; + } + + // Block C: Indices 13 to 18 + if (((src[13] | src[14] | src[15] | src[16] | src[17] | src[18]) & 0xFF00) != 0) + { + return false; + } + + if ( + table[src[13]] == 255 || table[src[14]] == 255 || table[src[15]] == 255 + || table[src[16]] == 255 || table[src[17]] == 255 || table[src[18]] == 255 + ) + { + return false; + } + + // Block D: Indices 19 to 25 + if (((src[19] | src[20] | src[21] | src[22] | src[23] | src[24] | src[25]) & 0xFF00) != 0) + { + return false; + } + + if ( + table[src[19]] == 255 || table[src[20]] == 255 || table[src[21]] == 255 + || table[src[22]] == 255 || table[src[23]] == 255 || table[src[24]] == 255 + || table[src[25]] == 255 + ) + { + return false; + } + } } return true; diff --git a/src/ByteAether.Ulid/Ulid.New.cs b/src/ByteAether.Ulid/Ulid.New.cs index 88d6ca4..825cf6f 100644 --- a/src/ByteAether.Ulid/Ulid.New.cs +++ b/src/ByteAether.Ulid/Ulid.New.cs @@ -202,9 +202,7 @@ private static void AcquireSpinLock() [SkipLocalsInit] #endif #if NETCOREAPP3_0_OR_GREATER - [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] -#else - [MethodImpl(MethodImplOptions.AggressiveInlining)] + [MethodImpl(MethodImplOptions.AggressiveOptimization)] #endif private static void FillRandom(ref byte ulidBytesRef, long timestamp, GenerationOptions options) { diff --git a/src/ByteAether.Ulid/Ulid.String.cs b/src/ByteAether.Ulid/Ulid.String.cs index 19c73d0..f186911 100644 --- a/src/ByteAether.Ulid/Ulid.String.cs +++ b/src/ByteAether.Ulid/Ulid.String.cs @@ -129,7 +129,9 @@ public override string ToString() [SkipLocalsInit] #endif #if NETCOREAPP3_0_OR_GREATER - [MethodImpl(MethodImplOptions.AggressiveOptimization)] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] +#else + [MethodImpl(MethodImplOptions.AggressiveInlining)] #endif public static Ulid Parse(ReadOnlySpan chars, IFormatProvider? provider = null) => ParseCore(chars); @@ -145,7 +147,9 @@ public static Ulid Parse(ReadOnlySpan chars, IFormatProvider? provider = n [SkipLocalsInit] #endif #if NETCOREAPP3_0_OR_GREATER - [MethodImpl(MethodImplOptions.AggressiveOptimization)] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] +#else + [MethodImpl(MethodImplOptions.AggressiveInlining)] #endif public static Ulid Parse(ReadOnlySpan bytes, IFormatProvider? provider = null) => ParseCore(bytes); @@ -154,9 +158,7 @@ public static Ulid Parse(ReadOnlySpan bytes, IFormatProvider? provider = n [SkipLocalsInit] #endif #if NETCOREAPP3_0_OR_GREATER - [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] -#else - [MethodImpl(MethodImplOptions.AggressiveInlining)] + [MethodImpl(MethodImplOptions.AggressiveOptimization)] #endif private static unsafe Ulid ParseCore(ReadOnlySpan input) where T : unmanaged @@ -259,7 +261,7 @@ private static unsafe Ulid ParseCore(ReadOnlySpan input) [MethodImpl(MethodImplOptions.AggressiveInlining)] #endif public static Ulid Parse(string s, IFormatProvider? provider = null) - => Parse(s.AsSpan()); + => ParseCore(s.AsSpan()); /// /// Attempts to parse a string representation of a ULID into a instance. @@ -292,7 +294,7 @@ public static bool TryParse(ReadOnlySpan s, IFormatProvider? provider, out { try { - result = Parse(s); + result = ParseCore(s); return true; } catch @@ -318,7 +320,7 @@ public static bool TryParse(ReadOnlySpan s, IFormatProvider? provider, out { try { - result = Parse(s); + result = ParseCore(s); return true; } catch @@ -399,41 +401,35 @@ public bool TryFormat( #endif private void Fill(Span span, T[] map) where T: unmanaged { - // Hints JIT to not do bounds-checking later - if (span.Length < UlidStringLength || map.Length < 1 << 5) - { - return; - } + // Encode randomness + span[25] = map[_r9 & 0x1F]; // [11111111][11111111][11111111][11111111][11111111][11111111][11111111][11111111][11111111][111|11111|] + span[24] = map[((_r8 & 0x3) << 3) | (_r9 >> 5)]; // [11111111][11111111][11111111][11111111][11111111][11111111][11111111][11111111][111111|11][111|11111] + span[23] = map[(_r8 >> 2) & 0x1F]; // [11111111][11111111][11111111][11111111][11111111][11111111][11111111][11111111][1|11111|11][11111111] + span[22] = map[((_r7 & 0xF) << 1) | (_r8 >> 7)]; // [11111111][11111111][11111111][11111111][11111111][11111111][11111111][1111|1111][1|1111111][11111111] + span[21] = map[((_r6 & 0x1) << 4) | (_r7 >> 4)]; // [11111111][11111111][11111111][11111111][11111111][11111111][1111111|1][1111|1111][11111111][11111111] + span[20] = map[(_r6 >> 1) & 0x1F]; // [11111111][11111111][11111111][11111111][11111111][11111111][11|11111|1][11111111][11111111][11111111] + span[19] = map[((_r5 & 0x7) << 2) | (_r6 >> 6)]; // [11111111][11111111][11111111][11111111][11111111][11111|111][11|111111][11111111][11111111][11111111] + span[18] = map[(_r5 >> 3) & 0x1F]; // [11111111][11111111][11111111][11111111][11111111][|11111|111][11111111][11111111][11111111][11111111] + span[17] = map[_r4 & 0x1F]; // [11111111][11111111][11111111][11111111][111|11111|][11111111][11111111][11111111][11111111][11111111] + span[16] = map[((_r3 & 0x3) << 3) | (_r4 >> 5)]; // [11111111][11111111][11111111][11111111][111111|11][111|11111][11111111][11111111][11111111][11111111] + span[15] = map[(_r3 >> 2) & 0x1F]; // [11111111][11111111][11111111][11111111][11111111][11111111][11111111][11111111][11111111][11111111] + span[14] = map[((_r2 & 0xF) << 1) | (_r3 >> 7)]; // [11111111][11111111][11111111][11111111][11111111][11111111][11111111][11111111][11111111][11111111] + span[13] = map[((_r1 & 0x1) << 4) | (_r2 >> 4)]; // [11111111][11111111][11111111][11111111][11111111][11111111][11111111][11111111][11111111][11111111] + span[12] = map[(_r1 >> 1) & 0x1F]; // [11111111][11111111][11111111][11111111][11111111][11111111][11111111][11111111][11111111][11111111] + span[11] = map[((_r0 & 0x7) << 2) | (_r1 >> 6)]; // [11111111][11111111][11111111][11111111][11111111][11111111][11111111][11111111][11111111][11111111] + span[10] = map[(_r0 >> 3) & 0x1F]; // [|11111|111][11111111][11111111][11111111][11111111][11111111][11111111][11111111][11111111][11111111] // Encode timestamp - span[0] = map[_t0 >> 5]; // |00[111|11111][11111111][11111111][11111111][11111111][11111111] - span[1] = map[_t0 & 0x1F]; // 00[111|11111|][11111111][11111111][11111111][11111111][11111111] - span[2] = map[_t1 >> 3]; // 00[11111111][|11111|111][11111111][11111111][11111111][11111111] - span[3] = map[((_t1 & 0x7) << 2) | (_t2 >> 6)]; // 00[11111111][11111|111][11|111111][11111111][11111111][11111111] - span[4] = map[(_t2 >> 1) & 0x1F]; // 00[11111111][11111111][11|11111|1][11111111][11111111][11111111] - span[5] = map[((_t2 & 0x1) << 4) | (_t3 >> 4)]; // 00[11111111][11111111][1111111|1][1111|1111][11111111][11111111] - span[6] = map[((_t3 & 0xF) << 1) | (_t4 >> 7)]; // 00[11111111][11111111][11111111][1111|1111][1|1111111][11111111] - span[7] = map[(_t4 >> 2) & 0x1F]; // 00[11111111][11111111][11111111][11111111][1|11111|11][11111111] - span[8] = map[((_t4 & 0x3) << 3) | (_t5 >> 5)]; // 00[11111111][11111111][11111111][11111111][111111|11][111|11111] span[9] = map[_t5 & 0x1F]; // 00[11111111][11111111][11111111][11111111][11111111][111|11111|] - - // Encode randomness - span[10] = map[(_r0 >> 3) & 0x1F]; // [|11111|111][11111111][11111111][11111111][11111111][11111111][11111111][11111111][11111111][11111111] - span[11] = map[((_r0 & 0x7) << 2) | (_r1 >> 6)]; // [11111111][11111111][11111111][11111111][11111111][11111111][11111111][11111111][11111111][11111111] - span[12] = map[(_r1 >> 1) & 0x1F]; // [11111111][11111111][11111111][11111111][11111111][11111111][11111111][11111111][11111111][11111111] - span[13] = map[((_r1 & 0x1) << 4) | (_r2 >> 4)]; // [11111111][11111111][11111111][11111111][11111111][11111111][11111111][11111111][11111111][11111111] - span[14] = map[((_r2 & 0xF) << 1) | (_r3 >> 7)]; // [11111111][11111111][11111111][11111111][11111111][11111111][11111111][11111111][11111111][11111111] - span[15] = map[(_r3 >> 2) & 0x1F]; // [11111111][11111111][11111111][11111111][11111111][11111111][11111111][11111111][11111111][11111111] - span[16] = map[((_r3 & 0x3) << 3) | (_r4 >> 5)]; // [11111111][11111111][11111111][11111111][111111|11][111|11111][11111111][11111111][11111111][11111111] - span[17] = map[_r4 & 0x1F]; // [11111111][11111111][11111111][11111111][111|11111|][11111111][11111111][11111111][11111111][11111111] - span[18] = map[(_r5 >> 3) & 0x1F]; // [11111111][11111111][11111111][11111111][11111111][|11111|111][11111111][11111111][11111111][11111111] - span[19] = map[((_r5 & 0x7) << 2) | (_r6 >> 6)]; // [11111111][11111111][11111111][11111111][11111111][11111|111][11|111111][11111111][11111111][11111111] - span[20] = map[(_r6 >> 1) & 0x1F]; // [11111111][11111111][11111111][11111111][11111111][11111111][11|11111|1][11111111][11111111][11111111] - span[21] = map[((_r6 & 0x1) << 4) | (_r7 >> 4)]; // [11111111][11111111][11111111][11111111][11111111][11111111][1111111|1][1111|1111][11111111][11111111] - span[22] = map[((_r7 & 0xF) << 1) | (_r8 >> 7)]; // [11111111][11111111][11111111][11111111][11111111][11111111][11111111][1111|1111][1|1111111][11111111] - span[23] = map[(_r8 >> 2) & 0x1F]; // [11111111][11111111][11111111][11111111][11111111][11111111][11111111][11111111][1|11111|11][11111111] - span[24] = map[((_r8 & 0x3) << 3) | (_r9 >> 5)]; // [11111111][11111111][11111111][11111111][11111111][11111111][11111111][11111111][111111|11][111|11111] - span[25] = map[_r9 & 0x1F]; // [11111111][11111111][11111111][11111111][11111111][11111111][11111111][11111111][11111111][111|11111|] + span[8] = map[((_t4 & 0x3) << 3) | (_t5 >> 5)]; // 00[11111111][11111111][11111111][11111111][111111|11][111|11111] + span[7] = map[(_t4 >> 2) & 0x1F]; // 00[11111111][11111111][11111111][11111111][1|11111|11][11111111] + span[6] = map[((_t3 & 0xF) << 1) | (_t4 >> 7)]; // 00[11111111][11111111][11111111][1111|1111][1|1111111][11111111] + span[5] = map[((_t2 & 0x1) << 4) | (_t3 >> 4)]; // 00[11111111][11111111][1111111|1][1111|1111][11111111][11111111] + span[4] = map[(_t2 >> 1) & 0x1F]; // 00[11111111][11111111][11|11111|1][11111111][11111111][11111111] + span[3] = map[((_t1 & 0x7) << 2) | (_t2 >> 6)]; // 00[11111111][11111|111][11|111111][11111111][11111111][11111111] + span[2] = map[_t1 >> 3]; // 00[11111111][|11111|111][11111111][11111111][11111111][11111111] + span[1] = map[_t0 & 0x1F]; // 00[111|11111|][11111111][11111111][11111111][11111111][11111111] + span[0] = map[_t0 >> 5]; // |00[111|11111][11111111][11111111][11111111][11111111][11111111] } /// diff --git a/src/ByteAether.Ulid/UlidJsonConverter.cs b/src/ByteAether.Ulid/UlidJsonConverter.cs index d6c0540..852ef4d 100644 --- a/src/ByteAether.Ulid/UlidJsonConverter.cs +++ b/src/ByteAether.Ulid/UlidJsonConverter.cs @@ -30,36 +30,25 @@ public override Ulid Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSer Span byteSpan = stackalloc byte[Ulid.UlidStringLength]; byteSequence.CopyTo(byteSpan); - Ulid.TryParse(byteSpan, null, out var ulid); - return ulid; + return Ulid.Parse(byteSpan); } else { var byteSpan = reader.ValueSpan; - if (byteSpan.Length != Ulid.UlidStringLength) - { - throw new JsonException($"Ulid invalid: length must be {Ulid.UlidStringLength}"); - } - - Ulid.TryParse(byteSpan, null, out var ulid); - return ulid; + return Ulid.Parse(byteSpan); } } - catch (IndexOutOfRangeException ex) + catch (FormatException ex) { throw new JsonException($"Ulid invalid: length must be {Ulid.UlidStringLength}", ex); } - catch (OverflowException ex) - { - throw new JsonException("Ulid invalid: invalid character", ex); - } } /// public override void Write(Utf8JsonWriter writer, Ulid ulid, JsonSerializerOptions options) { Span ulidString = stackalloc byte[Ulid.UlidStringLength]; - ulid.TryFormat(ulidString, out var _, []); + ulid.TryFormat(ulidString, out _, []); writer.WriteStringValue(ulidString); } @@ -68,7 +57,7 @@ public override void Write(Utf8JsonWriter writer, Ulid ulid, JsonSerializerOptio public override void WriteAsPropertyName(Utf8JsonWriter writer, Ulid ulid, JsonSerializerOptions options) { Span ulidString = stackalloc byte[Ulid.UlidStringLength]; - ulid.TryFormat(ulidString, out var _, []); + ulid.TryFormat(ulidString, out _, []); writer.WritePropertyName(ulidString); }