Skip to content

Commit 916c6dc

Browse files
cd21hhazzik
andauthored
Eliminate memory allocations in GuidCombGenerator under .NET 8+ (#3610)
Co-authored-by: Alex Zaytsev <[email protected]>
1 parent ba73c43 commit 916c6dc

File tree

3 files changed

+52
-20
lines changed

3 files changed

+52
-20
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
using System;
2+
using NHibernate.Id;
3+
using NUnit.Framework;
4+
5+
namespace NHibernate.Test.IdGen.GuidComb;
6+
7+
[TestFixture]
8+
public class GuidCombFixture
9+
{
10+
class GuidCombGeneratorEx : GuidCombGenerator
11+
{
12+
public static Guid Generate(string guid, DateTime utcNow) => GenerateComb(Guid.Parse(guid), utcNow);
13+
}
14+
15+
[Test]
16+
public void CanGenerateSequentialGuid()
17+
{
18+
Assert.AreEqual(Guid.Parse("076a04fa-ef4e-4093-8479-b0e10103cdc5"),
19+
GuidCombGeneratorEx.Generate(
20+
"076a04fa-ef4e-4093-8479-8599e96f14cf",
21+
new DateTime(2023, 12, 23, 15, 45, 55, DateTimeKind.Utc)),
22+
"seed: 076a04fa");
23+
24+
Assert.AreEqual(Guid.Parse("81162ee2-a4cb-4611-9327-d61f0137e5b6"),
25+
GuidCombGeneratorEx.Generate(
26+
"81162ee2-a4cb-4611-9327-23bbda36176c",
27+
new DateTime(2050, 01, 29, 18, 55, 35, DateTimeKind.Utc)),
28+
"seed: 81162ee2");
29+
30+
}
31+
32+
}

src/NHibernate/Async/Id/GuidCombGenerator.cs

+1
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99

1010

1111
using System;
12+
using System.Diagnostics;
1213
using NHibernate.Engine;
1314

1415
namespace NHibernate.Id

src/NHibernate/Id/GuidCombGenerator.cs

+19-20
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using System;
2+
using System.Diagnostics;
23
using NHibernate.Engine;
34

45
namespace NHibernate.Id
@@ -36,34 +37,32 @@ public partial class GuidCombGenerator : IIdentifierGenerator
3637
/// <returns>The new identifier as a <see cref="Guid"/>.</returns>
3738
public object Generate(ISessionImplementor session, object obj)
3839
{
39-
return GenerateComb();
40+
return GenerateComb(Guid.NewGuid(), DateTime.UtcNow);
4041
}
4142

4243
/// <summary>
4344
/// Generate a new <see cref="Guid"/> using the comb algorithm.
4445
/// </summary>
45-
private Guid GenerateComb()
46+
protected static Guid GenerateComb(Guid guid, DateTime utcNow)
4647
{
47-
byte[] guidArray = Guid.NewGuid().ToByteArray();
48-
49-
DateTime now = DateTime.UtcNow;
50-
48+
#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP2_1_OR_GREATER
49+
Span<byte> guidArray = stackalloc byte[16];
50+
guid.TryWriteBytes(guidArray);
51+
#else
52+
var guidArray = guid.ToByteArray();
53+
#endif
5154
// Get the days and milliseconds which will be used to build the byte string
52-
TimeSpan days = new TimeSpan(now.Ticks - BaseDateTicks);
53-
TimeSpan msecs = now.TimeOfDay;
54-
55-
// Convert to a byte array
55+
var ts = new TimeSpan(utcNow.Ticks - BaseDateTicks);
56+
var days = ts.Days;
57+
guidArray[10] = (byte) (days >> 8);
58+
guidArray[11] = (byte) days;
59+
5660
// Note that SQL Server is accurate to 1/300th of a millisecond so we divide by 3.333333
57-
byte[] daysArray = BitConverter.GetBytes(days.Days);
58-
byte[] msecsArray = BitConverter.GetBytes((long) (msecs.TotalMilliseconds / 3.333333));
59-
60-
// Reverse the bytes to match SQL Servers ordering
61-
Array.Reverse(daysArray);
62-
Array.Reverse(msecsArray);
63-
64-
// Copy the bytes into the guid
65-
Array.Copy(daysArray, daysArray.Length - 2, guidArray, guidArray.Length - 6, 2);
66-
Array.Copy(msecsArray, msecsArray.Length - 4, guidArray, guidArray.Length - 4, 4);
61+
var msecs = (long) (utcNow.TimeOfDay.TotalMilliseconds / 3.333333);
62+
guidArray[12] = (byte) (msecs >> 24);
63+
guidArray[13] = (byte) (msecs >> 16);
64+
guidArray[14] = (byte) (msecs >> 8);
65+
guidArray[15] = (byte) msecs;
6766

6867
return new Guid(guidArray);
6968
}

0 commit comments

Comments
 (0)