예제 #1
0
        /// <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);
            }
        }
예제 #2
0
        /// <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();
            }
        }
예제 #4
0
 private bool TryCreateIncrementalRandomSequence(RandomSequence6 previousRandomSequence, RandomSequence6 newRandomSequence, out RandomSequence6 largerRandomSequence)
 {
     return(previousRandomSequence.TryAddRandomBits(newRandomSequence, out largerRandomSequence));
 }
예제 #5
0
 /// <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());
 }