【问题标题】:Reducing peak memory consumption减少峰值内存消耗
【发布时间】:2016-01-28 21:53:04
【问题描述】:

为了简单起见,我使用下一个简化的代码生成位图:

for (int frameIndex = 0; frameIndex < 90; frameIndex++) {

    UIGraphicsBeginImageContextWithOptions(CGSizeMake(130, 130));

    // Making some rendering on the context.

    // Save the current snapshot from the context.
    UIImage *snapshot = UIGraphicsGetImageFromCurrentImageContext();
    [self.snapshots addObject:snapshot];

    UIGraphicsEndImageContext();
}

所以没有什么不重要的,但是当操作系统为所有内容提供大约 30 MB 的内存时,一切都变得复杂了(在这种特殊情况下,它是 watch OS 2,但它不是依赖于操作系统的问题)并且超出配额,操作系统只是杀死应用程序的进程。

分配工具的下一张图表说明了这个问题:

这是同一张图,但内存消耗的注释不同 - 在上述代码执行之前、此时和之后。可以看出,最终生成了大约 5.7 MB 的位图,这是绝对可以接受的结果。不能接受的是图表顶部的内存消耗 (44.6 MB) - 所有这些内存都被CoreUI: image data 吃掉了。鉴于操作发生在后台线程中,执行时间并不那么重要。

所以问题是:减少内存消耗(可能通过增加执行时间)以适应内存配额的正确方法是什么?为什么尽管调用了UIGraphicsEndImageContext,但内存消耗却增加了?

更新 1: 我认为使用 NSOperation、NSTimer 等拆分整个操作可以解决问题,但仍会尝试提出一些同步解决方案。

试图收集所有答案并测试下一段代码:

UIGraphicsBeginImageContextWithOptions(CGSizeMake(130, 130));
for (int frameIndex = 0; frameIndex < 45; frameIndex++) {

    // Making some rendering on the context.

    @autoreleasepool {
        UIImage *snapshot = UIGraphicsGetImageFromCurrentImageContext();
        [self.snapshots addObject:snapshot];
    }
    CGContextClearRect(UIGraphicsGetCurrentContext(), CGSizeMake(130, 130));
}

for (int frameIndex = 0; frameIndex < 45; frameIndex++) {

    // Making some rendering on the context.

    @autoreleasepool {
        UIImage *snapshot = UIGraphicsGetImageFromCurrentImageContext();
        [self.snapshots addObject:snapshot];
    }
    CGContextClearRect(UIGraphicsGetCurrentContext(), CGSizeMake(130, 130));
}
UIGraphicsEndImageContext();

发生了什么变化:

  1. 将 90 次迭代分成 45 次的 2 部分。
  2. 将图形上下文移到外部并在每次迭代后将其清除,而不是创建新上下文。
  3. 在自动释放池中打包并存储快照。

结果 - 没有任何改变,内存消耗保持在同一水平。

此外,如果完全取消拍摄和存储快照,它只会减少 4 MB 的内存消耗,即少于 10%。

更新 2:

每 3 秒通过计时器进行渲染生成下一张图:

正如您所见,即使渲染除以时间间隔,内存也没有被释放(准确地说 - 没有完全释放)。有些东西告诉我,在执行渲染的对象存在之前,内存不会被释放。

更新 3:

结合3种方法解决了这个问题:

  1. 将整个渲染任务拆分为子任务。例如,将 90 张图纸分成 6 个子任务,每个子任务 15 张图纸(15 张的数量是凭经验找到的)。
  2. 使用dispatch_after 以较小的间隔连续执行所有子任务(在我的情况下为 0.05 秒)。
  3. 最后也是最重要的。为了避免像上一张图​​那样的内存泄漏 - 每个子任务都应该在新对象的上下文中执行。例如:

    self.snapshots = [[SnaphotRender new] renderSnapshotsInRange:[0, 15]];

感谢大家的回答,但@EmilioPelaez 最接近正确答案。

【问题讨论】:

  • 为什么不创建一个计时器,它每秒调用一次生成图像的方法?
  • 减少内存消耗的最简单方法是减小从上下文中捕获的生成图像(self.contextSize)的大小
  • 图像的总字节大小可能是 130 * 130 * 4(每像素字节数)* 150 = ~10MB。不确定手表是否存在,但可能会建立临时内存,您可以尝试将 UIImage *snapshot = UIGraphicsGetImageFromCurrentImageContext(); [self.snapshots addObject:snapshot]; 包装在 @autoreleasepool 块中。
  • ... 另外,为什么不将UIGraphicsBeginImageContextWithOptionsUIGraphicsEndImageContext 移到循环之外呢?清除上下文并不难,无论您是否建立自己的临时池,您都绝对不会有 150 倍的处理和自动释放成本。
  • 您使用的是UIGraphicsBeginImageContextWithOptions 还是UIGraphicsBeginImageContext?在第一种情况下,您使用的是哪个比例因子?

标签: objective-c memory


【解决方案1】:

更正了更新后的问题帧数。

图像的总字节大小可能为 130 * 130 * 4(每像素字节数)* 90 = ~6MB。

不确定手表的情况,但可能正在建立临时内存,您可以尝试将快照代码包装在 @autoreleasepool 块中:

@autoreleasepool {
    UIImage *snapshot = UIGraphicsGetImageFromCurrentImageContext();
    [self.snapshots addObject:snapshot];
}

【讨论】:

  • 感谢您的回答。忘了提到我试图将整个循环包装在 autoreleasepool 块中,但内存消耗保持在同一水平。有点着急,是的,你的大小计算是正确的 - 我更正了代码,测试的位图的实际数量是 90,所以它在图表上大约是 6 MB。
【解决方案2】:

我认为您的问题是您在同一上下文中执行所有快照(for 循环所在的上下文)。我相信在上下文结束之前内存不会被释放,也就是图表下降的时候。

我建议您减少上下文的范围,因此不要使用 for 循环来绘制所有帧,而是使用一些 iVar 跟踪进度并仅绘制一帧;每当您完成渲染帧时,您都可以使用dispatch_after 再次调用该函数并修改变量。即使延迟为 0,也会允许上下文结束并清理不再使用的内存。

附言。当我指上下文时,我不是指图形上下文,而是指代码中的某个范围。

【讨论】:

  • @DmitryGutsulyak 我看到了你的编辑,我认为它不会改变任何东西,因为它仍然在同一个上下文中发生。更糟糕的是,自动释放池在谨慎方面会犯错,并且会比常规内存管理更长时间地保留您的对象。你试过我的建议了吗?
  • 更新了问题。不幸的是,即使除以时间间隔也不起作用。认为disptach_after 使用与NSTimer 相同的基本原则。
  • 埃米利奥谢谢。我已经用最终结果更新了问题。
  • @DmitryGutsulyak 很高兴我能帮上忙!
【解决方案3】:

等等,图像分辨率起着很大的作用,例如,尺寸为 5000 px * 3000 px 的 1 兆字节 jpeg 图像将消耗 60 MB 大小的内存,5000 * 3000 * 4 字节为 60 MB,图像得到解压到内存中,所以我们排错,所以首先请告诉我们你使用什么样的图像尺寸(尺寸)?

【讨论】:

  • 用用于图形生成的位图数量更新了问题。
  • 你能用你使用的图像分辨率(尺寸)更新我们吗? self.contextSize 的值是多少?
  • tip 1 : 尝试做 5 顿饭,我的意思是做 5 个 for 循环,第一个有 30 张图片,让它完成 然后做在完成之前的其他迭代,最终的 ram 可能是相同的,但峰值可能会下降。 提示 2: 如果您不在图像中使用 alpha,请将其关闭。 tip 3 : 尝试将手表内存中的图像写入文件而不是ram,暂时可以考虑这些临时文件,这取决于使用情况。祝你好运,我不知道我是否可以提供进一步的帮助。
  • 这个答案应该作为评论发布。但是最后一条评论似乎回答了这个问题,@Zazu 你能更新你的答案吗?否则这不是一个真正的答案。
  • 谢谢。也使用您的提示使用新示例更新了问题。但问题仍然存在。好像没有同步解决方案。
【解决方案4】:

编辑(澄清后):

我认为,正确的方法不应该是直接存储 UIImage 对象,而是压缩 NSData 对象(即使用 UIImageJPEGRepresentation)。然后,在需要时,再次将它们转换为 UIImage 对象。

但是,如果您同时使用其中的许多,您将很快耗尽内存。

原答案:

实际上总大小可能高于 10MB(可能 > 20MB),具体取决于缩放因子(如 here 所示)。请注意,UIGraphicsBeginImageContextWithOptionsUIGraphicsBeginImageContext 相比需要一个比例参数。所以我猜这与屏幕截图有某种关系不是吗?

还有方法UIGraphicsGetImageFromCurrentImageContextit's thread safe,所以它可能会返回一个副本或使用更多内存。然后你就有了 40MB。

This answer 表示 iOS 以某种方式存储未显示时压缩的图像。这可能是之后使用 6MB 的原因。

我的最终猜测是设备正在检测内存峰值,然后尝试以某种方式节省内存。由于图像没有被使用,它在内部压缩它们并回收内存。

所以我不会担心,因为它看起来像是在自己照顾它。但是您可以按照其他人的建议进行操作,将图像保存到文件中,如果您不打算使用它,请不要将其保存在内存中。

【讨论】:

  • 感谢您的回答。之后的 6MB 使用量就是我需要的问题是超过了 30MB 的内存限制(手表操作系统)。认为你没有得到正确的问题。更新了它。 UIGraphicsGetImageFromCurrentImageContext 有什么替代品吗?
  • 好的,那么您可以尝试直接存储对其数据的压缩引用,而不是存储UIImage 对象,例如使用UIImageJPEGRepresentation。您可以在需要时从该 NSData 对象直接创建一个 UIImage
  • 谢谢,但正如我在上面所写的,即使完全删除存储 UIImage,它也会将内存消耗减少不到 10%。所以问题出在图形上下文创建阶段的某个地方。
猜你喜欢
  • 2013-06-13
  • 2023-03-18
  • 2015-05-10
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2010-11-14
相关资源
最近更新 更多