GCD表现不佳

时间:2011-02-20 21:01:49

标签: objective-c cocoa concurrency grand-central-dispatch

您可能还记得,我正在尝试使用GCD来加速我的一些代码,即碰撞检测和分辨率引擎。但是,我显然做错了,因为我的所有GCD代码都比我的序列代码慢得多且不那么一致(慢了1.4倍到10倍之间)。请允许我举个例子:我以冒泡排序方式迭代数组,以确定该数组中对象之间可能发生的所有冲突:

- (double) detectCollisionsInArray:(NSArray*)objects
{   
    int count = [objects count];
    if (count > 0)
    {       
        double time = CFAbsoluteTimeGetCurrent();
        for (int i = 0; i < count; i++)
        {
            for (int j = i + 1; j < count; j++)
            {
                /** LOTS AND LOTS OF WORK FOR EACH OBJECT **/
            }
        }

        return CFAbsoluteTimeGetCurrent() - time;
    }

    return 0;
}

非常简单,考虑到问题的限制,它似乎表现良好。但是,我想利用以下事实:在代码部分中不修改每个对象的状态,并使用GCD来并行化这项工作。要做到这一点,我正在尝试这样的事情:

- (double) detectCollisionsInArray:(NSArray*)objects
{   
    int count = [objects count];
    if (count > 0)
    {
        NSOperationQueue* opQueue = [[NSOperationQueue alloc] init];
        NSBlockOperation* blockOperation = nil;

        double time = CFAbsoluteTimeGetCurrent();
        for (int i = 0; i < count; i++)
        {
            for (int j = i + 1; j < count; j++)
            {
                void (^workBlock) (void) = ^() 
                {
                    /** LOTS AND LOTS OF WORK FOR EACH OBJECT **/
                };

                if (!blockOperation)
                {
                    blockOperation = [NSBlockOperation blockOperationWithBlock:b];
                }
                else
                {
                    [blockOperation addExecutionBlock:workBlock];
                }
            }
        }

        [opQueue addOperation:blockOperation];
        [opQueue autorelease];

        return CFAbsoluteTimeGetCurrent() - time;
    }

    return 0;
}

任何人都可以帮助我走上正轨,并提供一个良好的GCD教程的链接吗?我查看了几个GCD教程并搜索了所有文档,我仍然觉得我对这个主题的掌握充其量是微不足道的。谢谢!

2 个答案:

答案 0 :(得分:30)

您是否有理由不使用GCD C API和dispatch_*系列函数?您无法控制NSOperationQueue的GCD方面(就像您要将块提交到哪个队列一样)。此外,我无法判断您是否使用iOS,但NSOperationQueue 在iOS上使用GCD。这可能是它催生这么多线程的原因。无论哪种方式,如果直接使用GCD API,您的代码将更短更简单:

- (double) detectCollisionsInArray:(NSArray*)objects
{   
  int count = [objects count];
  if (count > 0)
  {
    double time = CFAbsoluteTimeGetCurrent();

    dispatch_group_t group = dispatch_group_create();
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    for (int i = 0; i < count; i++)
    {
      dispatch_group_async(group, queue, ^{
        for (int j = i + 1; j < count; j++)
        {
          dispatch_group_async(group, queue, ^{
            /** LOTS AND LOTS OF WORK FOR EACH OBJECT **/
          });
        }
      });
    }
    dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
    dispatch_release(group);
    return CFAbsoluteTimeGetCurrent() - time;
  }
  return 0;
}

您可以使用dispatch_group将所有执行组合在一起,并等待所有执行完成dispatch_group_wait。如果您不想知道块何时完成,则可以忽略组部分并使用dispatch_asyncdispatch_get_global_queue函数将获得3个并发队列中的一个(低,默认或高优先级)供您提交块。您不必担心限制线程数或类似的东西。 GCD调度程序应该为您完成所有这些。只需确保提交到并发队列,该队列可以是3个全局队列之一,也可以是通过将DISPATCH_QUEUE_CONCURRENT传递给dispatch_queue_create创建的队列(这可以从OS X 10.7和iOS 5.0)。

如果您在每个块中执行某些文件I / O或对其他资源征税,则可能需要在GCD中进行控制并限制一次提交到队列的块数。这与限制NSOperationQueue中的并发操作计数具有相同的效果。您可以使用GCD信号量执行此操作:

- (double) detectCollisionsInArray:(NSArray*)objects
{   
  int count = [objects count];
  if (count > 0)
  {
    double time = CFAbsoluteTimeGetCurrent();

    dispatch_group_t group = dispatch_group_create();
    dispatch_semaphore_t semaphore = dispatch_semaphore_create(10);
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    for (int i = 0; i < count; i++)
    {
      dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
      dispatch_group_async(group, queue, ^{
        for (int j = i + 1; j < count; j++)
        {
          dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
          dispatch_group_async(group, queue, ^{
            /** LOTS AND LOTS OF WORK FOR EACH OBJECT **/
            dispatch_semaphore_signal(semaphore);
          });
        }
        dispatch_semaphore_signal(semaphore);
      });
    }
    dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
    dispatch_release(group);
    dispatch_release(semaphore);
    return CFAbsoluteTimeGetCurrent() - time;
  }
  return 0;
}

一旦掌握了它,GCD使用起来非常简单。我现在在我的代码中使用它。

  

任何人都可以帮助我走上正轨,并提供一个良好的GCD教程的链接吗?

运行,不要走到Mike Ash's blog。他在GCD上的系列节目是我见过的最清晰,最简洁的,只需要大约30分钟的时间来阅读整篇文章。 Apple的WWDC视频来自2010年的GCD和块也非常好。

答案 1 :(得分:4)

在您的代码中,您将延迟每个对象需要完成的工作,直到嵌套for循环结束。也就是说,当循环结束时,你将有一个操作,有很多块用于一堆对象,因此你不会正确地利用GCD。

我建议您为每个对象创建一个NSBlockOperation,并在每个NSOperationQueue次迭代结束时将其添加到for (int j = i + 1; j < count; j++)

这样,一旦迭代结束,系统就会开始处理你需要为每个对象做的工作。

还要记住,队列不应该比可用的处理器大得多,否则在线程切换过程中会有一些开销会降低速度。