27

Edit

I read through some articles on blocks and fast enumeration and GCD and the like. @Bbum, who's written many articles on the subject of GCD and blocks, says that the block enumeration methods are always as fast or faster than the fast enumeration equivalents. You can read his reasoning here.

While this has been a fascinating, intellectual conversation, I agree with those who said that it really depends on the task at hand.


I have some tasks to accomplish and I need them done fast, cheap, and efficiently. Apple gives us many choices for how we want to enumerate an array, but I'm not sure which to choose.

Fast Enumeration

for (id obj in array)
{
    /* Do something with |obj|. */
}

Nonconcurrent Block Enumeration

[array enumerateObjectsUsingBlock: ^(id obj, NSUInteger idx, BOOL *stop) {
    /* Do something with |obj|. */
}];

Concurrent Block Enumeration

[array enumerateObjectsWithOptions: NSEnumerationConcurrent usingBlock: ^(id obj, NSUInteger idx, BOOL *stop) {
    /* Do something with |obj|. */
}];

GCD Apply

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);
dispatch_apply([array count], queue, ^(size_t idx) {
    id obj = [array objectAtIndex: idx];
    /* Do something with |obj|. */
});

GCD Async Apply

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);
dispatch_async(queue, ^(void) {
    dispatch_apply([array count], queue, ^(size_t idx) {
        id obj = [array objectAtIndex: idx];
        /* Do something with |obj|. */
    });
});

Or perhaps something with NSBlockOperations or an NSOperationQueue?

TIA, Alex.

Community
  • 1
  • 1
Alexsander Akers
  • 15,967
  • 12
  • 58
  • 83
  • 3
    Can you compare async and non-async calls? What are the criteria for "efficiently"? Is "cheap" as important as "fast"? Etc... :) Might be helpful if there was a bit more information on your particular scenario? – Jedidja Jun 01 '11 at 01:22
  • 2
    Also see http://stackoverflow.com/questions/992901/how-do-i-iterate-over-an-nsarray. – rid Jun 01 '11 at 01:23
  • What do you mean can I compare then? I'm performing this enumeration on iOS, so it needs to be efficient and fast, but the same general concept should apply to Mac OS X as well. Just with iOS, you have less resources so I'm trying to be as efficient as possible. That's all. – Alexsander Akers Jun 01 '11 at 01:24
  • @Radu That doesn't answer my question in the slightest. Thanks anyway. – Alexsander Akers Jun 01 '11 at 01:24
  • @Alexsander Akers, if you read the accepted answer, you will find that the `for (id object in array)` construct is faster, and why. – rid Jun 01 '11 at 01:26
  • @Radu Well it's faster because it stores the pointers, but with the block methods you're iterating multiple objects at a time. – Alexsander Akers Jun 01 '11 at 01:31
  • @Radu I know that you have to spawn threads, but GCD and NSOperationQueue are supposed to take care of that for you, cf. http://cl.ly/7ELr – Alexsander Akers Jun 01 '11 at 01:39
  • 1
    There’s no single, absolute answer to your question. I’m voting to close it. –  Jun 01 '11 at 01:42
  • @Alexsander Akers, I added an answer to elaborate. – rid Jun 01 '11 at 01:44
  • 3
    Benchmark, benchmark, benchmark, benchmark, benchmark. Then do it again. – BoltClock Jun 01 '11 at 01:49
  • How many items are in the array? Just pick one method and go with it. – Black Frog Jun 01 '11 at 01:25

3 Answers3

44

The fastest code is the code that reaches the market first.

Seriously -- unless you have a measurable performance problem, this particular choice should occupy no more of your time than it takes to answer which of these patterns fits the most naturally with my project's style?

Note: adressing a performance problem by moving from serial to concurrent execution usually results having two problems; performance & concurrency.

bbum
  • 162,346
  • 23
  • 271
  • 359
4

It really depends on the task at hand.

Processing more than one iteration at a time requires spawning threads. If the logic in the iterations is parallelizable and takes more time than it would take to spawn a thread, then use threads. Also, if you have so many items in the array that it would take less to spawn a thread than to walk through the whole array, divide your array into a few pieces and process them in parallel.

Otherwise, spawning threads to iterate through the array is overhead. Even if the OS takes care of that for you, it still does need to spawn them. That takes time and resources and context switching at runtime (depending on the number of CPUs available, load, scheduler, etc).

It all comes down to whether spawning a new thread takes longer than walking through the whole array. You can find that out using the profiling tools.

rid
  • 61,078
  • 31
  • 152
  • 193
  • This is true. I think I should just try all of them and see which of them is fastest. – Alexsander Akers Jun 01 '11 at 01:48
  • Also, don't assume that if it's the fastest for your current array, that will hold true for the next array. If you have and array of 10 integers and all you need to do is add them, then obviously the fastest way will be to simply use `for`, even though the million items array with heavy objects benefits greatly from a GCD approach. – rid Jun 01 '11 at 01:51
  • And again, depending on the situation, it could be better to divide your array in smaller arrays that you can process in parallel. For example, if you only have 20 objects that take time to process, you might get better performance if you process 2 arrays of 10 objects each in parallel than if you make 20 threads. Profile. – rid Jun 01 '11 at 01:52
  • 1
    And again, depending on what operation is performed on the array elements, GCD can be a problem — for instance, if the operation is blocking, it could cause GCD to reach the maximum number of GCD threads. There are way too many variables to give an absolute answer. –  Jun 01 '11 at 01:54
  • The operation isn't blocking. I know that for sure. – Alexsander Akers Jun 01 '11 at 01:56
  • @Alex But your question doesn’t mention it. You’ve asked a question that is too broad. You don’t state what operation is being executed, you don’t state the size of the array, you don’t state the specifics of your target environment. Questions about performance are tricky, and the usual answer is: profile your code and avoid premature optimisation. –  Jun 01 '11 at 01:59
  • 1
    One of the things that makes GCD fast is that it doesn't have to create a thread for each new task, but instead dispatches tasks to existing threads. – Caleb Jun 01 '11 at 02:07
  • True. But in general, do you think for-in (fast enumeration) is faster or the 10.6/4.0+ block enumeration? – Alexsander Akers Jun 01 '11 at 02:07
  • @Alexsander Akers, take a look at this question http://stackoverflow.com/questions/4486622/when-to-use-enumerateobjectsusingblock-vs-for (especially at the accepted answer and the comment). – rid Jun 01 '11 at 02:12
  • @Radu Just saw the same question. I was about to post an update to the question. :P – Alexsander Akers Jun 01 '11 at 02:13
  • @Alex, I was just pointing out the problem with "...it *does* need to spawn them." In fact, it usually *doesn't* need to spawn them. I have no data as to the speed of the various enumeration styles, so I won't try to guess. I'm pretty sure you'd get a lot of upvotes if you measured each style with data sets of different sizes on several different devices and posted the results. – Caleb Jun 01 '11 at 02:20
3

If you watch the WWDC video "Hidden Development Gems in iOS7" (this question is older than that video granted), you will see that simple for loop enumerations can be better implemented using GCD's:

[array enumerateObjectsWithOptions: NSEnumerationConcurrent usingBlock: ^(id obj, NSUInteger idx, BOOL *stop) { /* Do something with |obj|. */ }];

Although it does depend on the task at hand to an extent and shipping software is of the most importance, there are definitely easy ways to keep performance issues from cropping up as a tech debt issue in the future when your application starts to scale. For example, you may have a large network fetch result. From this result, you may have to iterate over the fetched objects and update some model or series of models (not Core Data models as Core Data is not thread safe! Additional measures must be taken to work with Core Data on multiple threads. This is a deep topic you will want to research if you are considering working on Core Data from a multi-threading scenario). Considering you are not performing updates to the UI (as that is not something you would want to do off of the main thread), you can use this method to take full advantage of the hardware of your device. Instead of the for loop using only one core at a time, it will fully utilize the available cores in parallel, therefore increasing performance and potentially almost halving the time of execution. This is not something you will want to do without giving any thought to the array you are enumerating over and the tasks you are performing from within the block, but is useful for a wide array of cases if used carefully.

dotToString
  • 230
  • 2
  • 13
  • So you down-voted an answer backed by information provided by Apple's own evangelists due to your perception that I provided potentially dangerous information. The question was asking for illumination, which is exactly what I provided along with a reference to where I got said information. Next time you feel the need to write a terrible run-on sentence comment criticizing an answer, be sure to actually include why EXACTLY you are criticizing. Potential harm? Confusion? No. He wanted to loop through an array fast and cheap. Watch the video. This is EXACTLY what the evangelists suggest for this. – dotToString Apr 19 '14 at 02:20
  • "Although it does depend on the task at hand to an extent" -caveat – dotToString Apr 19 '14 at 02:30
  • "If you watch the WWDC video "Hidden Development Gems in iOS7"" -where to find further explanation – dotToString Apr 19 '14 at 02:31
  • So provide the further explanation in the **answer**, rather than pointing out to a video. Edit your answer with propert information on why this enumeration worked for you, and I will retract my downvote. – Léo Natan Apr 19 '14 at 03:11
  • let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/50999/discussion-between-dottostring-and-leo-natan) – dotToString Apr 19 '14 at 03:37
  • I removed my -1. Just note, Core Data is not thread safe, and accessing objects on different threads from the same context can be disastrous. It may work sometimes (when you modify only single objects, but the potential is there to explode. For example, if you modify a relationship in two threads at the same time, result is undefined, and can even crash. Further, if objects are faults, and you realize the same object from different threads, again, the result is undefined behavior and can lead to "dread"locks and crashes. – Léo Natan Apr 19 '14 at 03:57