Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

When to use NSEnumerationConcurrent

Every now and then, I notice that I'm using a block to iterate over a collection without writing to any shared data or causing any side effects. I consider adding in an NSEnumerationConcurrent option, then decide against it as I don't really understand when it's worth using.

So I've got a specific question, and a more general one.

First question: Here's a maybe slightly contrived example of using a block to do something trivial concurrently:

CGFloat GetAverageHeight(NSArray* people)
{
  NSUInteger count = [people count];
  CGFloat* heights = malloc(sizeof(CGFloat) * count);

  [people enumerateObjectsWithOptions: NSEnumerationConcurrent usingBlock:
  ^(id person, NSUInteger idx, BOOL* stop)
  {
    heights[idx] = [person height];
  }];

  CGFloat total= 0.0;
  for (size_t i = 0 ; i < count ; i++) total += heights[i];
  free(heights);
  return total / count;
}

Ignoring the fact that a non-concurrent enumeration could have just summed the height directly, without the need for calling malloc or the second half of the function, is there any point using NSEnumerationConcurrent here? Does the overhead of using GCD (or whatever NSEnumerationConcurrent does in the background) negate the gain of getting a trivial property concurrently? How much less trivial does the block's work need to get before it's worth using NSEnumerationConcurrent?

Second question: More generally, should I consider concurrency to be something I should use when I see an opportunity to do so (rationale: presumably the point of these APIs is that they make concurrency less of a special case and more part of the general makeup of a program), or just an optimisation that I should only use if I've found a specific performance issue and believe that concurrency is the answer (rationale: bugs in concurrent code are a nightmare to track down)?

like image 216
Chris Devereux Avatar asked Jul 24 '11 13:07

Chris Devereux


1 Answers

Generally, you'd only use concurrency when the operations to be performed are relatively "heavy". Even then, using the raw concurrency offered by enumerateObjectsWithOptions: could easily be problematic if the parallelism is the wrong granularity for the task at hand.

GCD is really damned efficient at enqueuing and processing stuff, but that code is quite likely going to end up calling malloc() to copy the block (depends on whether the block has unique captured state).

The answer to your second question fills many books, most useless.

Taking non-concurrent code and making it concurrent is generally a Very Hard Problem rife with nightmarish bugs. Yet, designing for concurrency up front can be exceptionally time consuming. Worse, implementing for future concurrency without actually using it just leads to a nightmarish debugging experience when you do turn it on.

One key point; when considering concurrency, focus on making entire sub-graphs of objects thread-isolated save for exceedingly well defined boundary API that spans threads/queues. A good example of this is Core Data.

like image 137
bbum Avatar answered Oct 04 '22 15:10

bbum