19

I need to generate a unique numeric ID to attach to an incoming request. This ID is used only temporarily to track the request and will be discarded once the request has completed processing. This ID will only be used in the context of this application but will need to be assigned in a high performance multi-threaded way.

I was thinking of using DateTime.Now.Ticks for this ID but would like to know if DateTime.Now.Ticks could still generate a colliding ID if simultaneous requests are being concurrently being processed?

If anyone could suggest a better way to generate these IDs (preferably one that is not Int64 like Tick is) in a multi-threaded environment, please let me know. Something as simple as an incrementing number would suffice even, if only I didn't have to lock the number before incrementing.

shyneman
  • 305
  • 1
  • 3
  • 11
  • 3
    What's wrong with using a GUID/UUID? – Nick Johnson Sep 28 '11 at 02:43
  • too much overhead to generate GUID. the IDs i need only need to be unique for the process (not global) and it is used only for the duration of each request. – shyneman Sep 28 '11 at 19:18
  • 1
    "Too much overhead"? A GUID only requires generating a few bytes of random data. It's not expensive. It sounds like you're engaging in premature optimisation. – Nick Johnson Sep 28 '11 at 23:10
  • 1
    maybe it is insignificant in actual time but from some basic testing, it looks to be about 10x slower than interlocked.increment with 8 concurrent threads. – shyneman Sep 28 '11 at 23:44
  • Fair enough then, if you're generating enough IDs that that matters. – Nick Johnson Sep 28 '11 at 23:47

7 Answers7

14

You just need to use a static variable that is incremented each time you want another unique value. You can make this thread safe and still very fast by using the Interlocked.Increment method...

// Declaration
private static int safeInstanceCount = 0;

// Usage
{
      ...
      Interlocked.Increment(ref safeInstanceCount);
      ...
}
Phil Wright
  • 22,580
  • 14
  • 83
  • 137
  • i was already considering interlock.increment but i was hoping to avoid any type of lock. however, the performance impact is probably negligible as you mentioned so i will probably just go with that rather than spending too much thing over-optimizing. thanks. – shyneman Sep 28 '11 at 00:54
  • The performance impact is not worth thinking about unless you are planning on calling it millions of times per minute. Plus it is dead easy to use. – Phil Wright Sep 28 '11 at 01:02
  • 1
    @PhilWright: On my linux box `time csharp <<< "int x; while (System.Threading.Interlocked.Increment(ref x)<1000000000);"` shows it does roughly 121,521,448 increments per second (2.6x slower than just `x++`. So (as ever) it would actually depend on _what else_ needed to fit in that second whether you'd need to optimize anything. – sehe Sep 28 '11 at 08:36
  • 1
    However, performance degrades when threading: `ThreadStart a = () => { int x=0; while(Interlocked.Increment(ref x) < 1000000000); };` in parallel x 4 takes 9s; sharing the `int x` (by taking it out of the lambda) it takes 32s (_only ~31 miljon incr/s_) -- whilst the number of overall calls to Interlocked.Increment is effectively 4x as small :) – sehe Sep 28 '11 at 09:44
  • Interesting feedback. I suspect that the time taken to interlock.increment is small compared to the time of the actual operation being performed with it has been provided. – Phil Wright Sep 28 '11 at 11:43
  • 2
    I think you should change the example as the question asked for a way to generate ids and however implements it may make a mistake by writing his code this way: Interlocked.Increment(ref safeInstanceCount); Id = safeInstanceCount; writing: Id = Interlocked.Increment(ref safeInstanceCount); will explain the solution better. #IMHO – MosheG Mar 05 '17 at 15:48
7

DateTime.Now is absolutely terrible for this purpose. At best, you'll have a resolution of 1 millisecond; at worst, 17 ms on NT and 1 second (!) on CE/Compact Framework.

Consider using Interlocked.Increment method for a fast, thread-safe counter.

Jeffrey Hantin
  • 35,734
  • 7
  • 75
  • 94
  • [This article](http://msdn.microsoft.com/en-us/library/system.datetime.ticks.aspx) says: " There are 10,000 ticks in a millisecond." – PeterX Oct 27 '13 at 23:34
  • @PeterX The definition of 1 tick = 100ns in no way implies that the implementation of `DateTime.Now` is capable of such precision; I've encountered all the implementation limitations I mentioned in my post. If you really need microsecond-class precision, [`Stopwatch`](http://msdn.microsoft.com/en-us/library/system.diagnostics.stopwatch.aspx) does it better. – Jeffrey Hantin Oct 28 '13 at 23:09
  • No worries - yeah, I've moved to `Guid` just to ensure my temp folders - which are cleaned-up anyway - are unique. – PeterX Oct 29 '13 at 02:37
4

Start with a per-thread ID (if multiple threads are originating the requests), concatenated with a per-thread counter (if each thread is expected to originate more than one request).

comingstorm
  • 25,557
  • 3
  • 43
  • 67
2

Just get a strong random number or use a GUID

If high performance is the MUST have feature, allocate sequential numbers in monotonous sequence. Prevent lock contention by 'reserving' a range of (say, 20-100) ID's per thread that handles messages. That way, you'll need to lock the sequence generator only once in 20-100 iterations.

sehe
  • 374,641
  • 47
  • 450
  • 633
  • 1
    Using random seems like you might "randomly" have a collision. – Kirk Woll Sep 28 '11 at 15:04
  • performance is a must, that's why i'm shying away from guids or having to compute the ID. the reservation of a range of IDs for each thread is a neat idea. i'll mull over that one a bit. thanks. – shyneman Sep 28 '11 at 19:20
  • @KirkWoll: not if random is random enough. It is widely accepted that a GUID can be assumed not to clash for that reason. – sehe Sep 28 '11 at 19:25
1

If you know how many threads you're going to have (or at least an upper bound), you can divide your ID space up between your threads, computing the ID as the value of a (thread local) counter and the thread's ID - eg, counter_value++ << 8 | thread_id. Thus, no coordination or locking between threads is required, and generating an ID requires only an increment, a bitshift, and an or.

If you use the system thread ID for this, your IDs will be slightly longer, but you don't need to manually assign IDs to your threads.

Nick Johnson
  • 100,655
  • 16
  • 128
  • 198
-1

Can use this property but pay 1ms which is not important!

public static long UniqId { 
    get { 
        Thread.Sleep(1); 
        return long.Parse(DateTime.Now.ToString("yyMMddHHmmssffff")); 
    } 
}
MohsenB
  • 1,669
  • 18
  • 29
-1

I am using a simple generation Id based on time,It might help.

private static readonly string prefixNumber = ConfigManager.Current.GetAppSetting("AppTimeIdPrefix", "");
private static readonly DateTime epoch = DateTime.SpecifyKind(DateTime.Parse(ConfigManager.Current.GetAppSetting("AppTimeIdEpoch", "1970/01/01")), DateTimeKind.Utc);
/// <summary>
/// DateTime.Now is only updated every 10-15ms.
/// </summary>
private static long lastTime = CurrentTimeMilliseconds();
private static readonly object timeLock = new object();

private static long CurrentTimeMilliseconds()
{
   return (long)(DateTime.UtcNow.ToUniversalTime() - epoch).TotalMilliseconds;
}

public static long CreateId()
{
    lock (timeLock)
    { // prevent concurrent access to ensure uniqueness
        long original, newValue;
       do
      {
         original = lastTime;
         newValue = Math.Max(CurrentTimeMilliseconds(), original + 1);
      } while (Interlocked.CompareExchange(ref lastTime, newValue, original) != original);

        return long.Parse(string.Format("{0}{1}", prefixNumber, newValue));
     }
}

refer to How to ensure a timestamp is always unique?

colin
  • 1
  • Using the `Interlocked` class inside a lock-protected region defeats the purpose of using the `Interlocked` in the first place (avoiding the lock-induced contention). – Theodor Zoulias Oct 21 '21 at 04:18
  • Exactly. I completely agree with you on that. thank you! the lock object was removed from the above code. – Colin Chen Dec 15 '22 at 13:54