/// <summary> /// <para> /// Locking. /// </para> /// <para> /// Creates the values required to create an ID. /// </para> /// </summary> private (ulong Timestamp, RandomSequence6 RandomSequence) CreateValues() { // The random number generator is likely to lock, so doing this outside of our own lock is likely to increase throughput var randomSequence = CreateRandomSequence(); lock (this._lockObject) { var timestamp = this.GetTimestamp(); // If the clock has not advanced since the previous invocation if (timestamp == this.PreviousCreationTimestamp) { // If we can create more contiguous values, advance the count and create the next value if (this.TryCreateIncrementalRandomSequence(this.PreviousRandomSequence, randomSequence, out var largerRandomSequence)) { this.PreviousRandomSequence = largerRandomSequence; this.CurrentTimestampCreationCount++; return(timestamp, largerRandomSequence); } // Otherwise, sleep until the clock has advanced else { timestamp = this.AwaitUpdatedClockValue(); } } // Update the previous timestamp and reset the counter this.PreviousCreationTimestamp = timestamp; this.CurrentTimestampCreationCount = 1U; this.PreviousRandomSequence = randomSequence; return(timestamp, randomSequence); } }
/// <summary> /// <para> /// Pure function. /// </para> /// <para> /// Creates a new ID based on the given values. /// </para> /// </summary> /// <param name="timestamp">The UTC timestamp in milliseconds since the epoch.</param> /// <param name="randomSequence">A random sequence whose 2 low bytes are zeros. This is checked to ensure that the caller has understood what will be used.</param> internal decimal CreateCore(ulong timestamp, RandomSequence6 randomSequence) { // 93 bits fit into 28 decimals // 96 bits: [3 unused bits] [45 time bits] [48 random bits] Span <byte> bytes = stackalloc byte[2 + 12 + 2]; // Bits: 16 padding (to treat the left half as ulong) + 96 useful + 16 padding (to treat the right half as ulong) // Populate the left half with the timestamp { // The 64-bit timestamp's 19 high bits must be zero, leaving the low 45 bits to be used if (timestamp >> 45 != 0UL) { throw new InvalidOperationException($"{nameof(DistributedId)} has run out of available time bits."); // Year 3084 } // Write the time component into the first 8 bytes (64 bits: 16 padding to write a ulong, 3 unused, 45 used) BinaryPrimitives.WriteUInt64BigEndian(bytes, timestamp); } bytes = bytes[2..]; // Disregard the left padding
private static void Main() { /* * // Attempt to calculate probabilities * { * const int bits = 42; * const int servers = 100; * const int rate = 64; * * // Probability on one (rate-exhausted) timestamp for one server to have NO collisions with one other server * var prob = 1.0; * for (var i = 0UL; i < rate; i++) * { * var probForI = ((1UL << bits) - rate - i) / (double)((1UL << bits) - i); * prob *= probForI; * } * * // To the power of the number of distinct server pairs * // Gives us the probability that there are no collisions on that timestamp among all of the servers * prob = Math.Pow(prob, servers * (servers - 1) / 2); * * // Probability of one or more collisions on one (rate-exhausted) timestamp * // We will pretend this is the probability of just one collision, although it is technically one OR MORE * var collisionProb = 1 - prob; * * var collisionsPerId = collisionProb / (servers * Rate); * * Console.WriteLine(collisionsPerId); * var idsPerCollision = 1 / collisionsPerId; * * Console.WriteLine($"Calculated 1 collision in {(ulong)idsPerCollision:#,##0} IDs."); * }*/ // Calculate average maximum generation rate { var tempResults = new List <int>(); for (var i = 0; i < 100; i++) { var rate = 1; var previousValue = RandomSequence6.Create(); while (previousValue.TryAddRandomBits(RandomSequence6.Create(), out previousValue)) { rate++; } tempResults.Add(rate); } var lowRate = tempResults.Min(); var highRate = tempResults.Max(); var avgRate = tempResults.Average(); Console.WriteLine($"Low {lowRate}, high {highRate}, avg {avgRate}"); } var logInterval = TimeSpan.FromSeconds(10); var sw = Stopwatch.StartNew(); #pragma warning disable CS4014 // Deliberately unawaited background task LogAtIntervals(sw); // Unawaited task #pragma warning restore CS4014 while (true) { IterationCount++; Parallel.For(0, Parallelism, ProcessArray); FindDuplicates(); ResetGenerationCounts(); } }
private bool TryCreateIncrementalRandomSequence(RandomSequence6 previousRandomSequence, RandomSequence6 newRandomSequence, out RandomSequence6 largerRandomSequence) { return(previousRandomSequence.TryAddRandomBits(newRandomSequence, out largerRandomSequence)); }
/// <summary> /// <para> /// Pure function (although the random number generator may use locking internally). /// </para> /// <para> /// Returns a new 48-bit (6-byte) random sequence. /// </para> /// </summary> private RandomSequence6 CreateRandomSequence() { return(RandomSequence6.Create()); }