随着Grand Central Dispatch的快速增长

时间:2013-04-26 06:05:02

标签: ios grand-central-dispatch

上下文:我有一个使用GCD的iOS游戏应用程序。对于应用程序,我有三个队列:主队列,游戏逻辑队列(自定义序列),物理队列(自定义序列)。 Physics Queue用于进行物理模拟,Game Queue用于进行游戏逻辑。因此,对于每次更新(每1/60秒),每个队列执行其各自的工作,然后通过调度其他队列上的块与其他队列共享。

问题:

使用GCD:当我玩游戏级别,即队列正在做一些工作时,我看到我的堆/分配增长非常快,导致应用程序因内存问题而崩溃。如果我退出关卡并进入非游戏视图,即队列没有做任何工作,则内存会慢慢下降(大约需要2分钟)并变得稳定。在附图中,图像中的峰值就在我退出游戏关卡并出现之前。之后,当对象被解除分配时,内存会稳定下降。

没有GCD:如果我禁用其他两个队列并运行主队列上的所有内容,即我从代码中消除所有并发,我没有看到任何显着的堆增长,游戏运行正常

已在互联网上研究/尝试/研究过: 我简要了解了块捕获的概念以及被复制到堆中的块但是不太确定。据我所知,我无法在我的代码中找到任何此类对象,因为当我退出游戏级别并进入非游戏视图时,所有预期要解除分配的对象都将被释放。

问题:

  1. 使用GCD的App会创建很多块。创建大量块是一种好习惯吗?
  2. 在运行仪器时,我发现快速分配但未被释放的对象属于Malloc 48类。这些对象的负责库是libsystem_blocks.dylib,负责调用者是_Block_copy_internal。一旦我离开游戏关卡,即当队列停止执行任何工作时,这些对象会慢慢解除分配。但是,解除分配非常缓慢,需要大约2分钟才能完全清理。有没有办法加快这种清理工作?我怀疑对象是否堆积在一起然后导致内存崩溃。
  3. 关于可能发生的事情的任何想法?

    提前致谢。 enter image description here

    根据以下评论中的建议,我编写了以下测试代码。我基本上安排了从CADisplayLink回拨,然后在回调中我安排了5000个块到自定义队列。

    // In a simple bare-bones view controller template I wrote the following code
    
    - (void)viewDidLoad
    {
        [super viewDidLoad];
        self.objDisplayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(loop:)];
        [self.objDisplayLink setFrameInterval:1/60];
        [self.objDisplayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
    }
    
    - (void)loop:(CADisplayLink*)lobjDisplayLink
    {
        static int lintNumBlocks = 0;   
    
        if (lintNumBlocks < 5000)
        {
            dispatch_async(self.testQueueTwo,
                           ^{
                               @autoreleasepool
                               {
                                   NSLog(@"Block Number ; %d", lintNumBlocks);
    
                                   int outerIndex = 1000;
                                   while (outerIndex--)
                                   {
                                       NSLog(@"Printing (%d, %d)", outerIndex, lintNumBlocks);
                                   }
    
                                   dispatch_async(dispatch_get_main_queue(),
                                                  ^{
                                                      @autoreleasepool
                                                      {
                                                          NSString* lstrString = [NSString stringWithFormat:@"Finished Block %d", lintNumBlocks];
                                                          self.objDisplayLabel.text = lstrString;
                                                      }
                                                  });
                               }
                           });
    
            lintNumBlocks++;
        }
        else
        {
            self.objDisplayLabel.text = @"Finished Running all blocks";
            [self.objDisplayLink invalidate];
        }
    }
    

    此代码还提供了与前一篇文章中提到的GCD相同的堆增长。但令人惊讶的是,在这段代码中,内存永远不会降回到初始级别。仪器输出如下:

    enter image description here

    这段代码有什么问题?任何想法都会有所帮助。

3 个答案:

答案 0 :(得分:3)

当我每隔60秒触发一次CADisplayLink时,我在GCD队列周围看到了同样的内存堆积,但帧渲染块的耗时要长于完成。块会堆积在队列中,正如您所看到的那样,它们会产生一些与之相关的开销。

迈克·阿什有a great writeup about this,他在那里展示了建立加工区块的后果,以及减轻这些压力的方法。此外,最近一次关于GCD的WWDC会议以及如何在仪器中诊断这一点已经涵盖了这一点,但我现在找不到具体的会议。

在我的情况下,我最终使用类似于Mike所得的东西,并且我使用调度信号量来防止在内存中积累块。我在this answer中描述了这种方法,以及代码。我所做的是使用最大计数为1的信号量,然后在调度新块之前检查它。如果那个类型的另一个块坐在串行队列上,我保释并且不会在堆上扔另一个。块执行完毕后,我会减少信号量计数,以便添加另一个。

听起来你需要这样的东西来管理队列中新块的添加,因为你希望能够在游戏中增加负载时丢帧。

答案 1 :(得分:0)

主队列有一个自动释放池,在runloop的每次迭代中耗尽

并发队列没有想到..至少NSThreads默认不

将您的代码包装在@autoreleasepool

中的队列中

答案 2 :(得分:0)

尝试使用dispatch_async_f而不是dispatch_async。它避免了块复制。