diff --git a/Id Generator Snowflake Benchmarks/Benchmarks/IdGeneratorBenchmarks.cs b/Id Generator Snowflake Benchmarks/Benchmarks/IdGeneratorBenchmarks.cs index 487a2c3..846d6e1 100644 --- a/Id Generator Snowflake Benchmarks/Benchmarks/IdGeneratorBenchmarks.cs +++ b/Id Generator Snowflake Benchmarks/Benchmarks/IdGeneratorBenchmarks.cs @@ -9,13 +9,13 @@ namespace Id_Generator_Snowflake_Benchmarks.Benchmarks; public class IdGeneratorBenchmarks { #region Fields - private IdGenerator? _idGenerator; // Đối tượng IdGenerator để tạo ID - private HashSet? _generatedIds; // Danh sách ID đã được tạo + private IdGenerator? _idGenerator; // The IdGenerator object for generating IDs + private HashSet? _generatedIds; // The list of generated IDs #endregion #region Properties /// - /// Số lượng ID cần tạo + /// The number of IDs to generate. /// [Params(1_000, 10_000, 100_000, 1_000_000)] public int Size { get; set; } @@ -23,17 +23,17 @@ public class IdGeneratorBenchmarks #region Methods /// - /// Thiết lập ban đầu trước khi chạy bài kiểm tra. + /// Initial setup before running the benchmark. /// [GlobalSetup] public void Setup() { _idGenerator = new IdGenerator(0, 0); - _generatedIds = new HashSet(); + _generatedIds = new HashSet(); } /// - /// Tạo ID bằng cách gọi phương thức NextId từ đối tượng IdGenerator. + /// Generate IDs by calling the NextId method from the IdGenerator object. /// [Benchmark] public void GenerateIds() => For(0, Size, index => diff --git a/Id Generator Snowflake Benchmarks/Program.cs b/Id Generator Snowflake Benchmarks/Program.cs index 0000614..a4ed6e8 100644 --- a/Id Generator Snowflake Benchmarks/Program.cs +++ b/Id Generator Snowflake Benchmarks/Program.cs @@ -4,43 +4,82 @@ using Id_Generator_Snowflake; using System.Diagnostics; using System.Text; -using static System.Threading.Tasks.Parallel; using static Id_Generator_Snowflake.IdGenerator; +using static System.Threading.Tasks.Parallel; +using static System.Threading.Thread; -var idGen = new IdGenerator(0, 2); -var genIds = new HashSet(); -var numIds = 1; +var idGen = new IdGenerator(0, 0); +var genIds = new HashSet(); +var numIds = 100; var top = 10; +var flow = 1; -var sw = new Stopwatch(); -sw.Start(); -For(0, numIds, index => +switch (flow) { - var id = idGen.NextId(); - WriteLine("ID Generated: " + id); - lock (genIds) - { - if (!genIds.Add(id)) + case 1: { - WriteLine(new StringBuilder().Append("Handle duplicate ID error: ").Append(id).ToString()); + numIds = 1000000; + + var sw = new Stopwatch(); + + sw.Start(); + + For(0, numIds, index => + { + var id = idGen.NextId(); + + lock (genIds) + { + if (!genIds.Add(id)) + { + WriteLine(new StringBuilder().Append("Handle duplicate ID error: ").Append(id).ToString()); + } + } + }); + + sw.Stop(); + + var idsCnt = genIds.Count; + + if (numIds != idsCnt) + { + WriteLine($"Handle mismatch between expected and actual number of generated IDs: {idsCnt} / {numIds}"); + } + + WriteLine($"Time: {sw.ElapsedMilliseconds:#,#} ms\n"); + + break; } - } - WriteLine($"ID Parsed - Time: {IdGenerator.ParseId(id).Item1} - WorkerID: {IdGenerator.ParseId(id).Item2} - DatacenterID: {IdGenerator.ParseId(id).Item3}"); -}); -sw.Stop(); + case 2: + { + for (var i = 0; i < numIds; i++) + { + var id = idGen.NextId(); -var idsCnt = genIds.Count; -if (numIds != idsCnt) -{ - WriteLine($"Handle mismatch between expected and actual number of generated IDs: {idsCnt} / {numIds}"); -} + Sleep(100); + + lock (genIds) + { + if (!genIds.Add(id)) + { + WriteLine(new StringBuilder().Append("Handle duplicate ID error: ").Append(id).ToString()); + } + } + } -WriteLine($"Time: {sw.ElapsedMilliseconds:#,#} ms\n"); + break; + } + default: + { + break; + } +} WriteLine($"Top {top:#,#} of {numIds:#,#} IDs:"); + foreach (var id in genIds.Take(top)) { - WriteLine(id); + WriteLine($"ID: {id} - Time: {ExtractIdComponents(id).Item1} - WorkerID: {ExtractIdComponents(id).Item2} - DatacenterID: {ExtractIdComponents(id).Item3}"); } #endif diff --git a/Id Generator Snowflake/Common/Constant.cs b/Id Generator Snowflake/Common/Constant.cs index adeccc7..083f160 100644 --- a/Id Generator Snowflake/Common/Constant.cs +++ b/Id Generator Snowflake/Common/Constant.cs @@ -2,15 +2,18 @@ internal static class Constant { - internal const int WKR_ID_BITS = 3; // Number of bits used to store the Worker Id - internal const int DC_ID_BITS = 2; // Number of bits used to store the Datacenter Id - internal const int SEQ_BITS = 8; // Number of bits used to store the Sequence + internal const int WKR_ID_BITS = 7; // Number of bits used to store the Worker Id + internal const int DC_ID_BITS = 5; // Number of bits used to store the Datacenter Id + internal const int SEQ_BITS = 11; // Number of bits used to store the Sequence - internal const ulong MAX_WKR_ID = (1 << WKR_ID_BITS) - 1; // Maximum value of the Worker Id - internal const ulong MAX_DC_ID = (1 << DC_ID_BITS) - 1; // Maximum value of the Datacenter Id + internal const long MAX_WKR_ID = -1 ^ (-1 << WKR_ID_BITS); // Maximum value of the Worker Id + internal const long MAX_DC_ID = -1 ^ (-1 << DC_ID_BITS); // Maximum value of the Datacenter Id + internal const long MAX_SEQ = -1 ^ (-1 << SEQ_BITS); // Maximum value of the Sequence - internal const ulong SEQ_MASK = (1 << SEQ_BITS) - 1; // Mask to retrieve the bits of the Sequence + internal const int WKR_ID_SHFT = SEQ_BITS; // Number of bits to shift left to store the Worker Id + internal const int DC_ID_SHFT = SEQ_BITS + WKR_ID_BITS; // Number of bits to shift left to store the Datacenter Id + internal const int TS_LEFT_SHFT = SEQ_BITS + WKR_ID_BITS + DC_ID_BITS; // Number of bits to shift left to store the Timestamp - internal const int WKR_ID_SHFT = SEQ_BITS; // Number of bits to shift left to store the Worker Id - internal const int DC_ID_SHFT = SEQ_BITS + WKR_ID_BITS; // Number of bits to shift left to store the Datacenter Id + // 01-01-2023 00:00:00 + internal const long TS_EPOCH = 1_672_531_200_000; // Unix timestamp representing the custom epoch for Snowflake IDs } diff --git a/Id Generator Snowflake/IdGenerator.cs b/Id Generator Snowflake/IdGenerator.cs index adf7e3f..4fbedfe 100644 --- a/Id Generator Snowflake/IdGenerator.cs +++ b/Id Generator Snowflake/IdGenerator.cs @@ -9,46 +9,43 @@ namespace Id_Generator_Snowflake; public class IdGenerator { #region Fields - private ulong _lastTimestamp = default; // Last timestamp recorded + private long _lastTimestamp = -1; // Last timestamp recorded private readonly object _lock = new(); // Lock object for access synchronization - - /// - /// Epoch timestamp (used as an offset for the timestamp) - /// - public const ulong Twepoch = 1_672_531_200_000; // 01/012023 00:00:00 - - /// - /// Number of bits to shift left to store the timestamp - /// - public const int TimestampLeftShift = SEQ_BITS + WKR_ID_BITS + DC_ID_BITS; #endregion #region Properties /// /// Worker Id of the IdGenerator /// - public ulong WorkerId { get; protected set; } + public long WorkerId { get; protected set; } /// /// Datacenter Id của IdGenerator /// - public ulong DatacenterId { get; protected set; } + public long DatacenterId { get; protected set; } /// /// Current sequence /// - public ulong Sequence { get; internal set; } + public long Sequence { get; internal set; } #endregion #region Constructors - public IdGenerator(ulong workerId, ulong datacenterId, ulong sequence = default) + /// + /// Initializes a new instance of the IdGenerator class with the specified worker ID, datacenter ID, and sequence. + /// + /// The ID of the worker. + /// The ID of the datacenter. + /// The initial sequence number. + /// Thrown when the worker ID or datacenter ID is invalid. + public IdGenerator(long workerId, long datacenterId, long sequence = default) { - if (workerId > MAX_WKR_ID) + if (workerId is < 0 or > MAX_WKR_ID) { throw new ArgumentException(new StringBuilder().Append(wkr_id_exc_pfx).Append(MAX_WKR_ID).Append(exc_sfx).ToString()); } - if (datacenterId > MAX_DC_ID) + if (datacenterId is < 0 or > MAX_DC_ID) { throw new ArgumentException(new StringBuilder().Append(dc_id_exc_pfx).Append(MAX_DC_ID).Append(exc_sfx).ToString()); } @@ -64,23 +61,23 @@ public IdGenerator(ulong workerId, ulong datacenterId, ulong sequence = default) /// Generate and return a new Id. /// /// A newly generated Id. - public ulong NextId() + public long NextId() { lock (_lock) { - var ts = TimeGen(); + var curTs = TimeGen(); - if (ts < _lastTimestamp) + if (curTs < _lastTimestamp) { throw new Exception(ts_exc); } - else if (ts == _lastTimestamp) + else if (curTs == _lastTimestamp) { - Sequence = (Sequence + 1) & SEQ_MASK; + Sequence = (Sequence + 1) & MAX_SEQ; if (Sequence is 0) { - ts = _lastTimestamp.TilNextMillis(); + curTs = _lastTimestamp.TilNextMillis(); } } else @@ -88,30 +85,17 @@ public ulong NextId() Sequence = default; } - _lastTimestamp = ts; + _lastTimestamp = curTs; - return ((ts - Twepoch) << TimestampLeftShift) | (DatacenterId << DC_ID_SHFT) | (WorkerId << WKR_ID_SHFT) | (Sequence & SEQ_MASK); + return ((curTs - TS_EPOCH) << TS_LEFT_SHFT) | (DatacenterId << DC_ID_SHFT) | (WorkerId << WKR_ID_SHFT) | (Sequence & MAX_SEQ); } } - /// - /// Parse an Id and return its components. - /// - /// The Id to be parsed. - /// - /// A tuple consisting of three elements: - /// - DateTime: Represents the time corresponding to the Id. - /// - ulong: WorkerId of the Id. - /// - ulong: DatacenterId of the Id. - /// - public static (DateTime, ulong, ulong) ParseId(ulong id) - { - var datacenterId = (id >> DC_ID_SHFT) & ((1UL << DC_ID_BITS) - 1); - var workerId = (id >> WKR_ID_SHFT) & ((1UL << WKR_ID_BITS) - 1); - var timestamp = (id >> TimestampLeftShift) + Twepoch; - var dateTime = FromUnixTimeMilliseconds((long)timestamp).UtcDateTime; - - return (dateTime, workerId, datacenterId); - } + /// + /// Extracts the timestamp, worker ID, and datacenter ID components from a Snowflake ID. + /// + /// The Snowflake ID to extract components from. + /// A tuple containing the extracted timestamp, worker ID, and datacenter ID. + public static (DateTime, long, long) ExtractIdComponents(long id) => (FromUnixTimeMilliseconds((id >> TS_LEFT_SHFT) + TS_EPOCH).UtcDateTime, (id >> WKR_ID_SHFT) & ((1 << WKR_ID_BITS) - 1), (id >> DC_ID_SHFT) & ((1 << DC_ID_BITS) - 1)); #endregion } diff --git a/Id Generator Snowflake/Utilities/Util.cs b/Id Generator Snowflake/Utilities/Util.cs index b566b28..476a307 100644 --- a/Id Generator Snowflake/Utilities/Util.cs +++ b/Id Generator Snowflake/Utilities/Util.cs @@ -7,20 +7,20 @@ internal static class Util /// /// Get the current timestamp. /// - internal static ulong TimeGen() => (ulong)UtcNow.ToUnixTimeMilliseconds(); + internal static long TimeGen() => UtcNow.ToUnixTimeMilliseconds(); /// /// Find the next timestamp greater than the last timestamp. /// - internal static ulong TilNextMillis(this ulong lastTimestamp) + internal static long TilNextMillis(this long lastTimestamp) { - var ts = TimeGen(); + var curTs = TimeGen(); - while (ts <= lastTimestamp) + while (curTs <= lastTimestamp) { - ts = TimeGen(); + curTs = TimeGen(); } - return ts; + return curTs; } }