From 3f3d58fc6763e3df5df7815bd73699215e5fe11e Mon Sep 17 00:00:00 2001
From: int21h <vk@alphacloud.net>
Date: Mon, 16 Sep 2024 12:02:03 -0400
Subject: [PATCH 1/3] Remove memory allocations in GuidCombGenerator under .NET
 8+

---
 .../IdGen/GuidComb/GuidCombFixture.cs         | 32 ++++++++++++++
 src/NHibernate/Id/GuidCombGenerator.cs        | 43 ++++++++++++++++---
 2 files changed, 69 insertions(+), 6 deletions(-)
 create mode 100644 src/NHibernate.Test/IdGen/GuidComb/GuidCombFixture.cs

diff --git a/src/NHibernate.Test/IdGen/GuidComb/GuidCombFixture.cs b/src/NHibernate.Test/IdGen/GuidComb/GuidCombFixture.cs
new file mode 100644
index 00000000000..b79bbb807b1
--- /dev/null
+++ b/src/NHibernate.Test/IdGen/GuidComb/GuidCombFixture.cs
@@ -0,0 +1,32 @@
+using System;
+using NHibernate.Id;
+using NUnit.Framework;
+
+namespace NHibernate.Test.IdGen.GuidComb;
+
+[TestFixture]
+public class GuidCombFixture
+{
+	class GuidCombGeneratorEx : GuidCombGenerator
+	{
+		public static Guid Generate(string guid, DateTime utcNow) => GenerateComb(Guid.Parse(guid), utcNow);
+	}
+
+	[Test]
+	public void CanGenerateSequentialGuid()
+	{
+		Assert.AreEqual(Guid.Parse("076a04fa-ef4e-4093-8479-b0e10103cdc5"),
+			GuidCombGeneratorEx.Generate(
+				"076a04fa-ef4e-4093-8479-8599e96f14cf",
+				new DateTime(2023, 12, 23, 15, 45, 55, DateTimeKind.Utc)),
+			"seed: 076a04fa");
+
+		Assert.AreEqual(Guid.Parse("81162ee2-a4cb-4611-9327-d61f0137e5b6"),
+		    GuidCombGeneratorEx.Generate(
+			    "81162ee2-a4cb-4611-9327-23bbda36176c",
+			    new DateTime(2050, 01, 29, 18, 55, 35, DateTimeKind.Utc)),
+			"seed: 81162ee2");
+
+	}
+	
+}
diff --git a/src/NHibernate/Id/GuidCombGenerator.cs b/src/NHibernate/Id/GuidCombGenerator.cs
index cb01227c979..076ef8bc1cc 100644
--- a/src/NHibernate/Id/GuidCombGenerator.cs
+++ b/src/NHibernate/Id/GuidCombGenerator.cs
@@ -1,4 +1,5 @@
 using System;
+using System.Diagnostics;
 using NHibernate.Engine;
 
 namespace NHibernate.Id
@@ -36,21 +37,50 @@ public partial class GuidCombGenerator : IIdentifierGenerator
 		/// <returns>The new identifier as a <see cref="Guid"/>.</returns>
 		public object Generate(ISessionImplementor session, object obj)
 		{
-			return GenerateComb();
+			return GenerateComb(Guid.NewGuid(), DateTime.UtcNow);
 		}
 
 		/// <summary>
 		/// Generate a new <see cref="Guid"/> using the comb algorithm.
 		/// </summary>
-		private Guid GenerateComb()
+		protected static Guid GenerateComb(in Guid guid, DateTime utcNow)
 		{
-			byte[] guidArray = Guid.NewGuid().ToByteArray();
+#if NET8_0_OR_GREATER
+			Span<byte> guidArray = stackalloc byte[16];
+			Span<byte> msecsArray = stackalloc byte[sizeof(long)];
+			Span<byte> daysArray = stackalloc byte[sizeof(int)];
 
-			DateTime now = DateTime.UtcNow;
+			var bytesWritten = guid.TryWriteBytes(guidArray);
+			Debug.Assert(bytesWritten);
 
 			// Get the days and milliseconds which will be used to build the byte string 
-			TimeSpan days = new TimeSpan(now.Ticks - BaseDateTicks);
-			TimeSpan msecs = now.TimeOfDay;
+			TimeSpan days = new TimeSpan(utcNow.Ticks - BaseDateTicks);
+			TimeSpan msecs = utcNow.TimeOfDay;
+
+			// Convert to a byte array 
+			// Note that SQL Server is accurate to 1/300th of a millisecond so we divide by 3.333333 
+
+			bytesWritten = BitConverter.TryWriteBytes(daysArray, days.Days)
+				&& BitConverter.TryWriteBytes(msecsArray, (long)(msecs.TotalMilliseconds / 3.333333));
+			Debug.Assert(bytesWritten);
+
+			msecsArray.Reverse();
+
+			// Copy the bytes into the guid 
+			//Array.Copy(daysArray, daysArray.Length - 2, guidArray, guidArray.Length - 6, 2);
+			guidArray[10] = daysArray[1];
+			guidArray[11] = daysArray[0];
+
+			//Array.Copy(msecsArray, msecsArray.Length - 4, guidArray, guidArray.Length - 4, 4);
+			msecsArray[^4..].CopyTo(guidArray[^4..]);
+			return new Guid(guidArray);
+#else
+
+			byte[] guidArray = guid.ToByteArray();
+
+			// Get the days and milliseconds which will be used to build the byte string 
+			TimeSpan days = new TimeSpan(utcNow.Ticks - BaseDateTicks);
+			TimeSpan msecs = utcNow.TimeOfDay;
 
 			// Convert to a byte array 
 			// 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()
 			Array.Copy(msecsArray, msecsArray.Length - 4, guidArray, guidArray.Length - 4, 4);
 
 			return new Guid(guidArray);
+#endif
 		}
 
 		#endregion

From 9b5714a86ea0bff5d109817e4561b017111b784f Mon Sep 17 00:00:00 2001
From: "github-actions[bot]" <github-actions[bot]@users.noreply.github.com>
Date: Wed, 25 Sep 2024 14:26:34 +0000
Subject: [PATCH 2/3] Generate async files

---
 src/NHibernate/Async/Id/GuidCombGenerator.cs | 1 +
 1 file changed, 1 insertion(+)

diff --git a/src/NHibernate/Async/Id/GuidCombGenerator.cs b/src/NHibernate/Async/Id/GuidCombGenerator.cs
index 59540f3d2ca..eb4ef6c7c63 100644
--- a/src/NHibernate/Async/Id/GuidCombGenerator.cs
+++ b/src/NHibernate/Async/Id/GuidCombGenerator.cs
@@ -9,6 +9,7 @@
 
 
 using System;
+using System.Diagnostics;
 using NHibernate.Engine;
 
 namespace NHibernate.Id

From b19ca2809c92d23ca6f1b5453e83ceb855eb42d0 Mon Sep 17 00:00:00 2001
From: Alex Zaytsev <hazzik@gmail.com>
Date: Tue, 11 Feb 2025 10:51:43 +1000
Subject: [PATCH 3/3] apply suggestion from comments

---
 src/NHibernate/Id/GuidCombGenerator.cs | 62 +++++++-------------------
 1 file changed, 15 insertions(+), 47 deletions(-)

diff --git a/src/NHibernate/Id/GuidCombGenerator.cs b/src/NHibernate/Id/GuidCombGenerator.cs
index 076ef8bc1cc..adbbd78196d 100644
--- a/src/NHibernate/Id/GuidCombGenerator.cs
+++ b/src/NHibernate/Id/GuidCombGenerator.cs
@@ -43,60 +43,28 @@ public object Generate(ISessionImplementor session, object obj)
 		/// <summary>
 		/// Generate a new <see cref="Guid"/> using the comb algorithm.
 		/// </summary>
-		protected static Guid GenerateComb(in Guid guid, DateTime utcNow)
+		protected static Guid GenerateComb(Guid guid, DateTime utcNow)
 		{
-#if NET8_0_OR_GREATER
+#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP2_1_OR_GREATER
 			Span<byte> guidArray = stackalloc byte[16];
-			Span<byte> msecsArray = stackalloc byte[sizeof(long)];
-			Span<byte> daysArray = stackalloc byte[sizeof(int)];
-
-			var bytesWritten = guid.TryWriteBytes(guidArray);
-			Debug.Assert(bytesWritten);
-
-			// Get the days and milliseconds which will be used to build the byte string 
-			TimeSpan days = new TimeSpan(utcNow.Ticks - BaseDateTicks);
-			TimeSpan msecs = utcNow.TimeOfDay;
-
-			// Convert to a byte array 
-			// Note that SQL Server is accurate to 1/300th of a millisecond so we divide by 3.333333 
-
-			bytesWritten = BitConverter.TryWriteBytes(daysArray, days.Days)
-				&& BitConverter.TryWriteBytes(msecsArray, (long)(msecs.TotalMilliseconds / 3.333333));
-			Debug.Assert(bytesWritten);
-
-			msecsArray.Reverse();
-
-			// Copy the bytes into the guid 
-			//Array.Copy(daysArray, daysArray.Length - 2, guidArray, guidArray.Length - 6, 2);
-			guidArray[10] = daysArray[1];
-			guidArray[11] = daysArray[0];
-
-			//Array.Copy(msecsArray, msecsArray.Length - 4, guidArray, guidArray.Length - 4, 4);
-			msecsArray[^4..].CopyTo(guidArray[^4..]);
-			return new Guid(guidArray);
+			guid.TryWriteBytes(guidArray);
 #else
-
-			byte[] guidArray = guid.ToByteArray();
-
+			var guidArray = guid.ToByteArray();
+#endif
 			// Get the days and milliseconds which will be used to build the byte string 
-			TimeSpan days = new TimeSpan(utcNow.Ticks - BaseDateTicks);
-			TimeSpan msecs = utcNow.TimeOfDay;
-
-			// Convert to a byte array 
+			var ts = new TimeSpan(utcNow.Ticks - BaseDateTicks);
+			var days = ts.Days;
+			guidArray[10] = (byte) (days >> 8);
+			guidArray[11] = (byte) days;
+					
 			// Note that SQL Server is accurate to 1/300th of a millisecond so we divide by 3.333333 
-			byte[] daysArray = BitConverter.GetBytes(days.Days);
-			byte[] msecsArray = BitConverter.GetBytes((long) (msecs.TotalMilliseconds / 3.333333));
-
-			// Reverse the bytes to match SQL Servers ordering 
-			Array.Reverse(daysArray);
-			Array.Reverse(msecsArray);
-
-			// Copy the bytes into the guid 
-			Array.Copy(daysArray, daysArray.Length - 2, guidArray, guidArray.Length - 6, 2);
-			Array.Copy(msecsArray, msecsArray.Length - 4, guidArray, guidArray.Length - 4, 4);
+			var msecs = (long) (utcNow.TimeOfDay.TotalMilliseconds / 3.333333);
+			guidArray[12] = (byte) (msecs >> 24);
+			guidArray[13] = (byte) (msecs >> 16);
+			guidArray[14] = (byte) (msecs >> 8);
+			guidArray[15] = (byte) msecs;
 
 			return new Guid(guidArray);
-#endif
 		}
 
 		#endregion