【问题标题】:Why is CGBitmapContextCreateImage slower than [UIImage initWithData:]?为什么 CGBitmapContextCreateImage 比 [UIImage initWithData:] 慢?
【发布时间】:2025-11-20 19:35:02
【问题描述】:

我目前正在开发一个应用程序,该应用程序一个接一个地显示许多图像。不幸的是,我没有为此使用视频的奢侈,但是,我可以选择使用的图像编解码器。数据从服务器发送到应用程序,已经编码。

例如,如果我使用 PNG 或 JPEG,我可以使用 [[UIImage alloc] initWithData:some_data] 将收到的数据转换为 UIImage。当我使用原始字节数组或另一个必须首先解码为原始字节数组的自定义编解码器时,我必须创建一个位图上下文,然后使用提供 CGImageRef 的CGBitmapContextCreateImage(bitmapContext),然后将其输入[[UIImage alloc] initWithImage:cg_image]。这要慢得多。

上图(时间以秒为单位)是执行从 NSData 到 UIImage 的转换所需的时间。 PNG、JPEG、BMP 和 GIF 都大致相同。 Null 根本不打扰转换并返回 nil 。 Raw 是使用位图上下文方法转换的原始 RGBA 字节数组。自定义的解压缩为原始格式,然后执行相同的操作。 LZ4 是原始数据,使用 LZ4 算法压缩,因此它也通过位图上下文方法运行。

例如,PNG 图像就是经过压缩的位图图像。这种解压缩然后渲染比我渲染原始图像所花费的时间更少。 iOS 必须在幕后做一些事情来加快速度。

如果我们查看转换每种类型需要多长时间以及绘制(到图形上下文)需要多长时间的图表,我们会得到以下信息:

我们可以看到大多数图像的转换时间非常不同,但绘制时间却非常相似。这排除了 UIImage 懒惰并仅在需要时转换的任何性能提升。

我的问题本质上是:我可以利用众所周知的编解码器的更快速度吗?或者,如果没有,是否有另一种方法可以更快地呈现我的原始数据?

编辑:为了记录,每当我得到一个新的 UIImage 时,我都会将这些图像绘制在另一个 UIImage 之上。可能有一种我愿意研究的更快的替代方案。但是,不幸的是,OpenGL 不是一种选择。

进一步编辑:这个问题相当重要,我想要最好的答案。在确保给出最佳答案的时间到期之前,不会颁发赏金。

最终编辑:我的问题是为什么解压缩和绘制原始 RGBA 数组比绘制 PNG 更快,例如,因为 PNG 必须解压缩为 RGBA 数组然后绘制。结果是它实际上更快。但是,这似乎只在发布版本中出现。调试版本没有为此优化,但在幕后运行的 UIImage 代码显然是优化的。通过编译为发布版本,RGBA 阵列图像比其他编解码器快得多。

【问题讨论】:

  • 如果没有更多信息,无法深入回答这个问题。你到底在做什么(发布一些代码)?为什么从 NSData 到 UIImage 的转换与您相关(似乎您排除了最相关的部分,显示)?您如何衡量所需的时间?
  • 我是否理解正确,您想找到通过网络发送图像、解压缩并在屏幕上显示它们而不在磁盘上缓存的最快方法?
  • 可以假设我已经在设备上拥有图像。它们是原始 RGBA 字节数组。我想尽快把它变成 UIImage。
  • 为什么将数据转换为与您相关的UIImage?您是否知道在屏幕上显示UIImage 所需的时间很大程度上取决于它的创建方式。您可能会找到创建UIImage 的最快方法,但这不太可能是显示此图像的最快方法。
  • 我没有意识到这一点。你有更多关于这方面的信息吗?

标签: ios objective-c uiimage cgbitmapcontextcreate


【解决方案1】:

在衡量性能时,衡量整个管道以发现瓶颈非常重要。

在您的情况下,这意味着您无法隔离 UIImage 创建。您必须包括图像显示,否则您会陷入仅测量您感兴趣的部分内容的陷阱。

UIImage 不是位图数据的薄包装器,而是一个相当复杂和优化的系统。例如,底层CGImage 可以只是对磁盘上某些压缩数据的引用。这就是为什么使用initWithContentsOfFile:initWithData: 初始化UIImage 速度很快。 iOS 中的 ImageIO 和 Quartz 框架中有更多隐藏的性能优化,所有这些都将添加到您的测量中。

获得可靠测量值的唯一可靠方法是做您真正想做的事情(从网络或磁盘获取数据,以某种方式创建 UIImage,并在屏幕上显示至少一帧)。

以下是您应该注意的一些注意事项:

  • Apple 的图形框架竭尽全力执行所需的最少工作。如果图像未显示,则可能永远无法解压缩。

  • 如果图像以低于其原始像素的分辨率显示,则可能只是部分解压缩(尤其是 JPEG)。这对于帮助优化可能是一件好事,但当然不能在从全图像分辨率的CGBitmapContext 创建图像时使用。所以除非必要,否则不要这样做。

  • 使用 Instruments 进行测量时,您可能看不到所有相关的 CPU 周期。图片的解压可以在backboardd(iOS 中使用的那种window-server)中进行。

  • 使用未压缩的图像似乎是最快的想法。但这确实忽略了这样一个事实,即内存可能是瓶颈,更少的数据(压缩图像)可以帮助解决这个问题。

结论:

您的目标应该是找到实际场景的瓶颈。所以不要使用虚构的测试数据和人为的代码进行测试。您最终可能会针对应用中未采用的代码路径优化性能。

当您将测试代码更改为测量完整管道时,如果您可以使用结果更新您的问题,那就太好了。

【讨论】:

  • 我相信 CIImages 是可能在内存或磁盘上的底层数据类型。正是那些可能导致 UIImage initWithContentsofFile 非常快。当存在内存压力时,它们也会丢弃数据。这也是真实世界场景的基准。图像被解压缩并绘制到一个完全不同的 UIImage。无论如何,这将强制减压。无论原始图像压缩是什么,将这些转换后的 UIImage 绘制到更大的 UIImage 所需的时间都是相同的,除了 GIF 最多需要两倍的时间。
  • @Velox CIImage 在 Core Image 中用于基于 GPU 的图像处理。加载或解压时一般不用。
  • @Velox 在优化速度时,绘制基于 CPU 的中间图像似乎是个坏主意。你不能使用视图/图层来组合图像吗?
  • 有可能。但就目前而言,我们更感兴趣的是为什么图像解压缩需要的时间比实际需要的要长。
  • @Velox 如果您需要帮助,请发布代码或可重现的说明。
【解决方案2】:

UIImage 使用和抽象最适合实际源的内部表示,因此具有良好的性能。 PNG 图像不会转换为位图,然后由UIImage 显示,而是更高效的绘图。

另一方面,位图是处理图像的最大且效率较低且繁重的方式,因此除了将它们转换为另一种格式之外,您无能为力。

【讨论】:

  • 这就是我想要解决的问题。我知道 iOS 在幕后做了其他事情。我正试图找出什么以及如何利用它。
【解决方案3】:

[UIImage initWithData:] 不会复制任何内存。它只是将内存留在原处,然后当您绘制它时,它会将内存转储到 GPU 上以完成它的工作 - 无需 CPU 或 RAM 大量参与解码图像。这一切都在 GPU 的专用硬件中完成。

请记住,Apple 通过授权其他制造商的技术并根据他们的需求进行定制来设计自己的 CPU/GPU。他们有 1000 多名 CPU 硬件工程师在一个芯片组上工作,高效处理图像是当务之急。

您的较低级别的代码可能会进行大量的内存复制和数学运算,这就是它慢得多的原因。

UIImageNSData 是非常智能的高性能 API,由真正了解(甚至构建)硬件和内核的人开发了数十年。除非您准备编写数千行代码并花费数月甚至数年的时间进行测试和调整以获得更好的性能,否则它们比您使用较低级别的 API 可以实现的效率更高。

NSData 例如,即使只有几 GB 的 RAM 可用,也可以毫不费力地处理数 TB 的数据并具有良好的性能 - 如果使用得当,它可以无缝结合 RAM 和 SSD/HDD 存储,通常具有与您类似的性能如果您实际上有 TB 的 RAM,并且UIImage 可以检测到内存不足的情况并释放几乎所有的 RAM,而无需代表您的任何代码 - 如果它知道最初加载图像的 URL(对文件更有效:/ / URLs 而不是 http:// URLs)。

如果您可以使用UIImageNSData 做您想做的事,那么您应该这样做。如果您有无法以其他方式实现的功能,请仅使用较低级别的 API。

【讨论】:

  • 我相信这就是正在发生的事情,但是我没有证据证明这一点。有什么可以在任何地方支持这一点吗?至于你的最后一段,我不完全确定你的意思。
  • 我只是在最后一段中说,如果可以的话,您应该使用高级 API。