Skip to content

Commit 3f3d58f

Browse files
committed
Remove memory allocations in GuidCombGenerator under .NET 8+
1 parent 8945299 commit 3f3d58f

File tree

2 files changed

+69
-6
lines changed

2 files changed

+69
-6
lines changed
Lines changed: 32 additions & 0 deletions
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/Id/GuidCombGenerator.cs

Lines changed: 37 additions & 6 deletions
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,21 +37,50 @@ 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(in Guid guid, DateTime utcNow)
4647
{
47-
byte[] guidArray = Guid.NewGuid().ToByteArray();
48+
#if NET8_0_OR_GREATER
49+
Span<byte> guidArray = stackalloc byte[16];
50+
Span<byte> msecsArray = stackalloc byte[sizeof(long)];
51+
Span<byte> daysArray = stackalloc byte[sizeof(int)];
4852

49-
DateTime now = DateTime.UtcNow;
53+
var bytesWritten = guid.TryWriteBytes(guidArray);
54+
Debug.Assert(bytesWritten);
5055

5156
// 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;
57+
TimeSpan days = new TimeSpan(utcNow.Ticks - BaseDateTicks);
58+
TimeSpan msecs = utcNow.TimeOfDay;
59+
60+
// Convert to a byte array
61+
// Note that SQL Server is accurate to 1/300th of a millisecond so we divide by 3.333333
62+
63+
bytesWritten = BitConverter.TryWriteBytes(daysArray, days.Days)
64+
&& BitConverter.TryWriteBytes(msecsArray, (long)(msecs.TotalMilliseconds / 3.333333));
65+
Debug.Assert(bytesWritten);
66+
67+
msecsArray.Reverse();
68+
69+
// Copy the bytes into the guid
70+
//Array.Copy(daysArray, daysArray.Length - 2, guidArray, guidArray.Length - 6, 2);
71+
guidArray[10] = daysArray[1];
72+
guidArray[11] = daysArray[0];
73+
74+
//Array.Copy(msecsArray, msecsArray.Length - 4, guidArray, guidArray.Length - 4, 4);
75+
msecsArray[^4..].CopyTo(guidArray[^4..]);
76+
return new Guid(guidArray);
77+
#else
78+
79+
byte[] guidArray = guid.ToByteArray();
80+
81+
// Get the days and milliseconds which will be used to build the byte string
82+
TimeSpan days = new TimeSpan(utcNow.Ticks - BaseDateTicks);
83+
TimeSpan msecs = utcNow.TimeOfDay;
5484

5585
// Convert to a byte array
5686
// Note that SQL Server is accurate to 1/300th of a millisecond so we divide by 3.333333
@@ -66,6 +96,7 @@ private Guid GenerateComb()
6696
Array.Copy(msecsArray, msecsArray.Length - 4, guidArray, guidArray.Length - 4, 4);
6797

6898
return new Guid(guidArray);
99+
#endif
69100
}
70101

71102
#endregion

0 commit comments

Comments
 (0)