From b32364698d34d3035b4e1be91fffbac445988c79 Mon Sep 17 00:00:00 2001 From: Matthieu Mourisson Date: Mon, 27 Oct 2025 21:02:38 +0100 Subject: [PATCH 1/3] Reduce allocated memory for name based UUID generation --- .../UUIDNext.Benchmarks.csproj | 2 +- Src/UUIDNext.Benchmarks/UuidBench.cs | 18 +++++++- Src/UUIDNext.sln | 43 ------------------- Src/UUIDNext.slnx | 6 +++ Src/UUIDNext/Tools/UuidToolkit.cs | 34 +++++++-------- 5 files changed, 39 insertions(+), 64 deletions(-) delete mode 100644 Src/UUIDNext.sln create mode 100644 Src/UUIDNext.slnx 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 } From d5214445b22d26f013ff13d97a090cdbf8328ace Mon Sep 17 00:00:00 2001 From: Matthieu Mourisson Date: Tue, 28 Oct 2025 00:09:59 +0100 Subject: [PATCH 2/3] Added benchmark action --- .github/workflows/benchmark.yml | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 .github/workflows/benchmark.yml diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml new file mode 100644 index 0000000..fd58bd0 --- /dev/null +++ b/.github/workflows/benchmark.yml @@ -0,0 +1,29 @@ +name: Benchmark + +on: workflow_dispatch + +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 From 7196446321ea209fac1375d84d0ed92eef222ac6 Mon Sep 17 00:00:00 2001 From: Matthieu Mourisson Date: Tue, 28 Oct 2025 00:20:56 +0100 Subject: [PATCH 3/3] try to fix security alert --- .github/workflows/benchmark.yml | 3 +++ .github/workflows/ci.yml | 3 +++ 2 files changed, 6 insertions(+) diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml index fd58bd0..48c1d35 100644 --- a/.github/workflows/benchmark.yml +++ b/.github/workflows/benchmark.yml @@ -2,6 +2,9 @@ name: Benchmark on: workflow_dispatch +permissions: + contents: read + defaults: run: working-directory: Src 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