Skip to content

Commit

Permalink
upgrade
Browse files Browse the repository at this point in the history
  • Loading branch information
Tynab committed Jun 14, 2023
1 parent 5008570 commit 777bb82
Show file tree
Hide file tree
Showing 5 changed files with 114 additions and 88 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -9,31 +9,31 @@ namespace Id_Generator_Snowflake_Benchmarks.Benchmarks;
public class IdGeneratorBenchmarks
{
#region Fields
private IdGenerator? _idGenerator; // Đối tượng IdGenerator để tạo ID
private HashSet<ulong>? _generatedIds; // Danh sách ID đã được tạo
private IdGenerator? _idGenerator; // The IdGenerator object for generating IDs
private HashSet<long>? _generatedIds; // The list of generated IDs
#endregion

#region Properties
/// <summary>
/// Số lượng ID cần tạo
/// The number of IDs to generate.
/// </summary>
[Params(1_000, 10_000, 100_000, 1_000_000)]
public int Size { get; set; }
#endregion

#region Methods
/// <summary>
/// Thiết lập ban đầu trước khi chạy bài kiểm tra.
/// Initial setup before running the benchmark.
/// </summary>
[GlobalSetup]
public void Setup()
{
_idGenerator = new IdGenerator(0, 0);
_generatedIds = new HashSet<ulong>();
_generatedIds = new HashSet<long>();
}

/// <summary>
/// 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.
/// </summary>
[Benchmark]
public void GenerateIds() => For(0, Size, index =>
Expand Down
87 changes: 63 additions & 24 deletions Id Generator Snowflake Benchmarks/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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<ulong>();
var numIds = 1;
var idGen = new IdGenerator(0, 0);
var genIds = new HashSet<long>();
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

Expand Down
19 changes: 11 additions & 8 deletions Id Generator Snowflake/Common/Constant.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
72 changes: 28 additions & 44 deletions Id Generator Snowflake/IdGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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

/// <summary>
/// Epoch timestamp (used as an offset for the timestamp)
/// </summary>
public const ulong Twepoch = 1_672_531_200_000; // 01/012023 00:00:00

/// <summary>
/// Number of bits to shift left to store the timestamp
/// </summary>
public const int TimestampLeftShift = SEQ_BITS + WKR_ID_BITS + DC_ID_BITS;
#endregion

#region Properties
/// <summary>
/// Worker Id of the IdGenerator
/// </summary>
public ulong WorkerId { get; protected set; }
public long WorkerId { get; protected set; }

/// <summary>
/// Datacenter Id của IdGenerator
/// </summary>
public ulong DatacenterId { get; protected set; }
public long DatacenterId { get; protected set; }

/// <summary>
/// Current sequence
/// </summary>
public ulong Sequence { get; internal set; }
public long Sequence { get; internal set; }
#endregion

#region Constructors
public IdGenerator(ulong workerId, ulong datacenterId, ulong sequence = default)
/// <summary>
/// Initializes a new instance of the IdGenerator class with the specified worker ID, datacenter ID, and sequence.
/// </summary>
/// <param name="workerId">The ID of the worker.</param>
/// <param name="datacenterId">The ID of the datacenter.</param>
/// <param name="sequence">The initial sequence number.</param>
/// <exception cref="ArgumentException">Thrown when the worker ID or datacenter ID is invalid.</exception>
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());
}
Expand All @@ -64,54 +61,41 @@ public IdGenerator(ulong workerId, ulong datacenterId, ulong sequence = default)
/// Generate and return a new Id.
/// </summary>
/// <returns>A newly generated Id.</returns>
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
{
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);
}
}

///<summary>
/// Parse an Id and return its components.
///</summary>
/// <param name="id">The Id to be parsed.</param>
/// <returns>
/// A tuple consisting of three elements:
/// - DateTime: Represents the time corresponding to the Id.
/// - ulong: WorkerId of the Id.
/// - ulong: DatacenterId of the Id.
///</returns>
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);
}
/// <summary>
/// Extracts the timestamp, worker ID, and datacenter ID components from a Snowflake ID.
/// </summary>
/// <param name="id">The Snowflake ID to extract components from.</param>
/// <returns>A tuple containing the extracted timestamp, worker ID, and datacenter ID.</returns>
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
}
12 changes: 6 additions & 6 deletions Id Generator Snowflake/Utilities/Util.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,20 +7,20 @@ internal static class Util
/// <summary>
/// Get the current timestamp.
/// </summary>
internal static ulong TimeGen() => (ulong)UtcNow.ToUnixTimeMilliseconds();
internal static long TimeGen() => UtcNow.ToUnixTimeMilliseconds();

/// <summary>
/// Find the next timestamp greater than the last timestamp.
/// </summary>
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;
}
}

0 comments on commit 777bb82

Please sign in to comment.