2

I have an array of ints that track the completion of 10,000 concurrent tasks, where the value is either 1 or 0. I think it would be more efficient if this array was an array of Bits and each concurrent thread used interlocked.CompareExchange (or similar) to alter a single bit.

How should I approach this if Interlocked doesn't have an overload for "bit"?

makerofthings7
  • 60,103
  • 53
  • 215
  • 448
  • Not on bit level AFAIK. Not even sure about the cpu. I know you get bitbanding for this in microcontrollers. – leppie Nov 27 '12 at 14:18
  • Why do you think it would be more efficient to have an array of bits? – jalf Nov 27 '12 at 14:22
  • @Jalf I envision checking if all 10,000 values are "1". If I do this as an int, then I think there will be 10,000 checks. If I do this as a bit I can do as many bits as the CPU will allow (64 bit on a 64 bit CPU?) – makerofthings7 Nov 27 '12 at 14:42
  • If you only want to check whether all 10000 tasks have completed, have you considered using a `CountdownEvent`? http://msdn.microsoft.com/en-us/library/system.threading.countdownevent.aspx – Richard Deeming Nov 27 '12 at 15:10
  • @makerofthings7: you still want to inspect each individual bit in isolation (it wouldn't be interlocked otherwise), so you'd still have 10,000 checks even if it could be done (it can't, because `Interlocked___` functions only work on the byte level) – jalf Nov 27 '12 at 18:23
  • @jalf In my case I only care if all 10000 are true. Any other combination means false. I'm using the bits to track the completion status of 10000 distinct actions. (really it's 2,000 but I'm adding scale for 8K more) – makerofthings7 Nov 27 '12 at 18:25
  • Yes, but again, it'd still require 10,000 interlocked operations, which are the expensive ones. Normal memory accesses are much, much cheaper. In short, no, what you're asking is not possible, and if it were, it wouldn't be a noticeable saving over 10,000 bools. But if you only care whether they're all true, can't you simply use a counter? Increment each time a task completes, and check if the counter has reached 10,000 yet (or split out into smaller batches with separate counters to avoid excessive contention) – jalf Nov 27 '12 at 18:48
  • @Jalf Wouldn't it be 10,000 operations divided by the width of an `int`? In that case I'm comparing a `byte[32]` to an `int.MaxValue()`. (32 bits in an `int` to all ones in the `maxvalue` ). Regardless if I expand on @Richard Deeming 's solution the operation savings would be 156 operations if I used a `ULong` (64 bits). The counter idea might work, but then I don't know which tasks haven't completed. I could use PLinq or TPL to do async comparisons on the 156 memory operations. What do you think? – makerofthings7 Nov 27 '12 at 23:07
  • You just said that you were *only* interested in whether they had *all* completed. Now you also want to know *which* tasks haven't yet completed. Anyway, if each task is supposed to set its own flag when it completes, then it'll be 10,000 operations because you have 10,000 tasks. Otherwise, it sounds like you're basically saying "I'll wait until all the tasks have completed, and then I will set all 10,000 flags, in 64-bit chunks, so I can efficiently indicate that all the tasks have completed", which... doesn't make sense ;) – jalf Nov 27 '12 at 23:18

4 Answers4

3

You can fake it with Interlocked.CompareExchange(ref int, int, int) in a loop, but I don't think it would be more efficient:

private static void SetBit(ref int valueToUpdate, int bitToSet)
{
   while (true)
   {
      int oldValue = valueToUpdate;
      int newValue = oldValue | bitToSet;
      int result = Interlocked.CompareExchange(ref valueToUpdate, newValue, oldValue);
      if (result == oldValue) break;
   }
}
Richard Deeming
  • 29,830
  • 10
  • 79
  • 151
1

I do not think you can do it with bits using Interlocked. You would have to switch to an array of ints, I believe, so that each bit is within its own int.

Interlocked.CompareExchange compares only for equality whereas you need to compare just a single bit of that int which you cannot do unless you use some other concurrency strategy to protect other changes to the int the bit resides within.

Paul Ruane
  • 37,459
  • 12
  • 63
  • 82
1

You need thread-safe constructs to track the completion of the concurrent tasks. Hence interlocked constructs are always faster than locks (because they don't need locks), you should use the static methods from the Interlocked-class with integers (0 as equivalent for false and 1 for true for example).

Jeffrey Richter does the same as he discribes in his book "CLR via C#" in chapter 28 "Primitive Thread Synchronization Constructs". He introduces own lock implementations based on Interlocked constructs in his Wintellect.PowerThreading library as well and gives an excellent explanation as well as time comparisons of his own implementations and those included in the .net framework.

Peit
  • 882
  • 6
  • 17
  • The reason I think using bits would be more efficient is because I will be doing 100 reads for every write. In other words I'll read the array of 10,000 ints, doing a compare for every write. (note This is just an illustration of load, not the actual implementation) – makerofthings7 Nov 27 '12 at 14:45
1

First, no, all Interlocked functions work on memory addresses, and individual bits are not addressable.

So what to do instead?

I would investigate two options:

  • don't bother with the array at all: have a simple counter, and InterlockedIncrement it each time a task completes. And to see if all tasks are done, just check if the value has reached 10,000 yet.
  • alternatively, use the array, in order to minimize (false) data sharing. But then you shouldn't try to pack it densely. Go the opposite way. Ensure that each entry ends up on a separate CPU cache line, so that tasks running in parallel won't fight over the same cache lines. (And then, since tasks all write to separate locations, they won't even have to set their "done" flag with an expensive Interlocked operation.)

I don't know which is fastest. Benchmark it, if performance matters. :)

jalf
  • 243,077
  • 51
  • 345
  • 550