2

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

问题:

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

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

已经在互联网上学习/尝试/研究过: 我对块捕获和复制到堆的块的概念有一个简要的了解,但不太确定。据我了解,我一直无法在我的代码中找到任何这样的对象,因为当我退出游戏关卡并进入非游戏视图时,所有预期被解除分配的对象都被解除分配。

问题:

  1. 带有 GCD 的 App 会创建很多块。创建很多块是一个好习惯吗?
  2. 在运行仪器时,我发现快速分配但未释放的对象属于 Malloc 48 类别。这些对象的责任库是 libsystem_blocks.dylib,责任调用者是 _Block_copy_internal。一旦我退出游戏关卡,即当队列停止执行任何工作时,这些对象就会慢慢被释放。但是,释放非常慢,需要大约 2 分钟才能完全清理。有什么办法可以加快清理速度?我的怀疑是对象不断堆积,然后导致内存崩溃。

关于可能发生的事情的任何想法?

提前致谢。 在此处输入图像描述

根据以下评论的建议,我编写了以下测试代码。我基本上安排了来自 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 相同的堆增长。但令人惊讶的是,在这段代码中,内存从未回落到其初始水平。仪器输出如下:

在此处输入图像描述

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

4

3 回答 3

3

当我每 60 秒触发一次 CADisplayLink 时,我在 GCD 队列周围看到了同样类型的内存累积,但是帧渲染块的完成时间比这更长。块将堆积在队列中,并且如您所见,它们有一些与之相关的开销。

Mike Ash 对此有一篇很棒的文章,他在其中展示了构建处理块的后果,以及减轻部分压力的方法。此外,最近在 GCD 上的 WWDC 会议中介绍了这一点,以及如何在 Instruments 中对此进行诊断,但我现在找不到具体的会议。

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

听起来您需要这样的东西来管理将新块添加到队列中,因为您希望能够随着游戏负载的增加而丢帧。

于 2013-04-26T15:12:28.803 回答
0

主队列有一个自动释放池,在运行循环的每次迭代中都会被耗尽

并发队列不认为.. 至少 NSThreads 默认情况下不会

将您的代码包装在@autoreleasepool 的队列中

于 2013-04-26T07:49:42.057 回答
0

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

于 2013-04-26T15:47:32.990 回答