diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml new file mode 100644 index 0000000..48c1d35 --- /dev/null +++ b/.github/workflows/benchmark.yml @@ -0,0 +1,32 @@ +name: Benchmark + +on: workflow_dispatch + +permissions: + contents: read + +defaults: + run: + working-directory: Src + +jobs: + build: + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + - name: Setup .NET + uses: actions/setup-dotnet@v4 + with: + dotnet-version: 9.0.x + + - name: Restore dependencies + run: dotnet restore + + - name: Build + run: dotnet build --no-restore + + - name: Run benchmark (net9.0) + run: dotnet run bench --framework net9.0 --configuration Release + working-directory: Src/UUIDNext.Benchmarks \ No newline at end of file diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7232374..c5028d7 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -9,6 +9,9 @@ on: pull_request: branches: [ "main" ] +permissions: + contents: read + defaults: run: working-directory: Src diff --git a/Src/UUIDNext.Benchmarks/UUIDNext.Benchmarks.csproj b/Src/UUIDNext.Benchmarks/UUIDNext.Benchmarks.csproj index fb6cb1b..1d10564 100644 --- a/Src/UUIDNext.Benchmarks/UUIDNext.Benchmarks.csproj +++ b/Src/UUIDNext.Benchmarks/UUIDNext.Benchmarks.csproj @@ -2,7 +2,7 @@ Exe - net8.0;net9.0 + net8.0;net9.0;net4.8 enable enable 12 diff --git a/Src/UUIDNext.Benchmarks/UuidBench.cs b/Src/UUIDNext.Benchmarks/UuidBench.cs index 0be9621..e363457 100644 --- a/Src/UUIDNext.Benchmarks/UuidBench.cs +++ b/Src/UUIDNext.Benchmarks/UuidBench.cs @@ -5,7 +5,20 @@ namespace UUIDNext.Benchmarks; [MemoryDiagnoser(false)] public class UuidBench { + private const string ShortUrl = "http://www.example.com"; private static readonly Guid urlNamespaceId = Guid.Parse("6ba7b811-9dad-11d1-80b4-00c04fd430c8"); + + private static readonly string longUrl = $"{ShortUrl}/?token={GetHexString(1024)}"; + private static string GetHexString(int stringLength) + { + Random rng = new(); + byte[] buffer = new byte[stringLength / 2]; + rng.NextBytes(buffer); + return BitConverter.ToString(buffer) + .Replace("-", "") + .ToLower(); + } + private static readonly Generator.UuidV8SqlServerGenerator uuidV8Generator = new(); private static readonly Generator.UuidV7FromSpecificDateGenerator uuidV7Generator = new(); @@ -21,7 +34,10 @@ public class UuidBench public Guid NewUuidV4() => Uuid.NewRandom(); [Benchmark] - public Guid NewUuidV5() => Uuid.NewNameBased(urlNamespaceId, "http://www.example.com"); + public Guid NewUuidV5_short() => Uuid.NewNameBased(urlNamespaceId, ShortUrl); + + [Benchmark] + public Guid NewUuidV5_long() => Uuid.NewNameBased(urlNamespaceId, longUrl); [Benchmark] public Guid NewUuidV7() => Uuid.NewSequential(); diff --git a/Src/UUIDNext.sln b/Src/UUIDNext.sln deleted file mode 100644 index 2b082a4..0000000 --- a/Src/UUIDNext.sln +++ /dev/null @@ -1,43 +0,0 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 17 -VisualStudioVersion = 17.1.32414.318 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "UUIDNext", "UUIDNext\UUIDNext.csproj", "{8E3E0676-2117-439D-B71C-A80578300D38}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "UUIDNext.Test", "UUIDNext.Test\UUIDNext.Test.csproj", "{C0E00001-67F1-4669-9494-DB5F8288523E}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "UUIDNext.Benchmarks", "UUIDNext.Benchmarks\UUIDNext.Benchmarks.csproj", "{02F45FD2-6598-4464-BAC4-88EAEC65B735}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UUIDNext.Cli", "UUIDNext.Cli\UUIDNext.Cli.csproj", "{09417083-8148-4875-BBAF-B1DDEA9C6C66}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Release|Any CPU = Release|Any CPU - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {8E3E0676-2117-439D-B71C-A80578300D38}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {8E3E0676-2117-439D-B71C-A80578300D38}.Debug|Any CPU.Build.0 = Debug|Any CPU - {8E3E0676-2117-439D-B71C-A80578300D38}.Release|Any CPU.ActiveCfg = Release|Any CPU - {8E3E0676-2117-439D-B71C-A80578300D38}.Release|Any CPU.Build.0 = Release|Any CPU - {C0E00001-67F1-4669-9494-DB5F8288523E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {C0E00001-67F1-4669-9494-DB5F8288523E}.Debug|Any CPU.Build.0 = Debug|Any CPU - {C0E00001-67F1-4669-9494-DB5F8288523E}.Release|Any CPU.ActiveCfg = Release|Any CPU - {C0E00001-67F1-4669-9494-DB5F8288523E}.Release|Any CPU.Build.0 = Release|Any CPU - {02F45FD2-6598-4464-BAC4-88EAEC65B735}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {02F45FD2-6598-4464-BAC4-88EAEC65B735}.Debug|Any CPU.Build.0 = Debug|Any CPU - {02F45FD2-6598-4464-BAC4-88EAEC65B735}.Release|Any CPU.ActiveCfg = Release|Any CPU - {02F45FD2-6598-4464-BAC4-88EAEC65B735}.Release|Any CPU.Build.0 = Release|Any CPU - {09417083-8148-4875-BBAF-B1DDEA9C6C66}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {09417083-8148-4875-BBAF-B1DDEA9C6C66}.Debug|Any CPU.Build.0 = Debug|Any CPU - {09417083-8148-4875-BBAF-B1DDEA9C6C66}.Release|Any CPU.ActiveCfg = Release|Any CPU - {09417083-8148-4875-BBAF-B1DDEA9C6C66}.Release|Any CPU.Build.0 = Release|Any CPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {1A6AB557-C717-4070-BB12-4F0F32A66042} - EndGlobalSection -EndGlobal diff --git a/Src/UUIDNext.slnx b/Src/UUIDNext.slnx new file mode 100644 index 0000000..35adb77 --- /dev/null +++ b/Src/UUIDNext.slnx @@ -0,0 +1,6 @@ + + + + + + diff --git a/Src/UUIDNext/Tools/UuidToolkit.cs b/Src/UUIDNext/Tools/UuidToolkit.cs index 7108e4c..2ce01dc 100644 --- a/Src/UUIDNext/Tools/UuidToolkit.cs +++ b/Src/UUIDNext/Tools/UuidToolkit.cs @@ -87,44 +87,40 @@ public static Guid CreateUuidFromName(Guid namespaceId, string name, HashAlgorit /// internal static Guid CreateUuidFromName(Guid namespaceId, string name, HashAlgorithm hashAlgorithm, byte version) { + const int namespaceBytesCount = 16; //Convert the name to a canonical sequence of octets (as defined by the standards or conventions of its name space); var normalizedName = name.Normalize(NormalizationForm.FormC); var utf8NameByteCount = Encoding.UTF8.GetByteCount(normalizedName); + int bytesToHashCount = namespaceBytesCount + utf8NameByteCount; #if NETSTANDARD2_0 - byte[] utf8NameBytes = new byte[utf8NameByteCount]; - Encoding.UTF8.GetBytes(normalizedName, 0, normalizedName.Length, utf8NameBytes, 0); + byte[] bytesToHash = new byte[bytesToHashCount]; - //put the name space ID in network byte order. - Span namespaceBytes = stackalloc byte[16]; + //put the namespace ID in network byte order. + Span namespaceBytes = bytesToHash.AsSpan(0, namespaceBytesCount); namespaceId.TryWriteBytes(namespaceBytes, bigEndian: true, out _); - //Compute the hash of the name space ID concatenated with the name. - int bytesToHashCount = namespaceBytes.Length + utf8NameBytes.Length; - byte[] bytesToHash = new byte[bytesToHashCount]; - namespaceBytes.CopyTo(bytesToHash); - utf8NameBytes.CopyTo(bytesToHash, namespaceBytes.Length); + Encoding.UTF8.GetBytes(normalizedName, 0, normalizedName.Length, bytesToHash, namespaceBytesCount); + //Compute the hash of the namespace ID concatenated with the name. var hash = hashAlgorithm.ComputeHash(bytesToHash); return CreateGuidFromBigEndianBytes(hash.AsSpan(0, 16), version); #else - Span utf8NameBytes = (utf8NameByteCount > 256) ? new byte[utf8NameByteCount] : stackalloc byte[utf8NameByteCount]; - Encoding.UTF8.GetBytes(normalizedName, utf8NameBytes); + const int stackallocMaxSize = 512; + Span bytesToHash = (utf8NameByteCount > stackallocMaxSize) ? new byte[bytesToHashCount] : stackalloc byte[bytesToHashCount]; - //put the name space ID in network byte order. - Span namespaceBytes = stackalloc byte[16]; + //put the namespace ID in network byte order. + Span namespaceBytes = bytesToHash[..namespaceBytesCount]; namespaceId.TryWriteBytes(namespaceBytes, bigEndian: true, out _); - //Compute the hash of the name space ID concatenated with the name. - int bytesToHashCount = namespaceBytes.Length + utf8NameBytes.Length; - Span bytesToHash = (utf8NameByteCount > 256) ? new byte[bytesToHashCount] : stackalloc byte[bytesToHashCount]; - namespaceBytes.CopyTo(bytesToHash); - utf8NameBytes.CopyTo(bytesToHash[namespaceBytes.Length..]); + Span utf8NameBytes = bytesToHash[namespaceBytesCount..]; + Encoding.UTF8.GetBytes(normalizedName, utf8NameBytes); + //Compute the hash of the namespace ID concatenated with the name. Span hash = stackalloc byte[hashAlgorithm.HashSize / 8]; hashAlgorithm.TryComputeHash(bytesToHash, hash, out _); - return CreateGuidFromBigEndianBytes(hash[0..16], version); + return CreateGuidFromBigEndianBytes(hash[..16], version); #endif }