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);
}