为什么CGBitmapContextCreateImage比[UIImage initWithData:]慢?

时间:2014-08-08 16:16:15

标签: ios objective-c uiimage cgbitmapcontextcreate

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

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

Image Conversion Times

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

例如,PNG图像只是压缩的位图图像。此解压缩然后渲染所需的时间比Raw图像的渲染时间短。 iOS必须在幕后做一些事情才能加快速度。

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

Drawing and conversion times

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

我的问题基本上是:我可以利用的知名编解码器的速度更快吗?或者,如果没有,还有另一种方法可以更快地渲染原始数据吗?

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

进一步编辑:这个问题非常重要,我希望得到最好的答案。奖金将在到期之前颁发,以确保给出最佳答案。

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

3 个答案:

答案 0 :(得分:1)

在衡量绩效时,衡量整个管道以找出瓶颈非常重要。

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

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

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

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

  • Apple的图形框架竭尽全力完成必要的最小工作。如果没有显示图像,则可能永远不会解压缩。

  • 如果图像的分辨率低于原始像素,则可能只是部分解压缩(特别是JPEG可能)。这对于优化有帮助,但在从CGBitmapContext完整图像分辨率创建图像时当然不能使用。所以除非必要,否则不要这样做。

  • 使用Instruments进行测量时,可能无法看到所有相关的CPU周期。图像解压缩可以在backboardd(iOS中使用的窗口服务器类型)中进行。

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

<强>结论:

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

当您将测试代码更改为测量完整管道时,如果您可以使用结果更新问题,那将会很不错。

答案 1 :(得分:0)

UIImage使用最适合实际来源的抽象内部表示,从而获得良好的性能。 PNG图像不会转换为位图,然后由UIImage显示,但效果更高。

另一方面,位图是处理图像的最大且效率最低且最重的方式,所以除了将它们转换为另一种格式之外,你无法做到这一点。

答案 2 :(得分:0)

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

请记住,Apple通过许可其他制造商的技术和定制来设计自己的CPU / GPU以满足他们的需求。他们有超过一千名CPU硬件工程师只在一个芯片组上工作,有效处理图像是一个优先事项。

你的低级代码可能正在进行大量的内存复制和数学运算,这就是为什么它的速度要慢得多。

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

例如,{p> NSData可以毫不费力地处理具有良好性能的TB级数据,即使只有几GB的RAM可用 - 正确使用它会将RAM和SSD / HDD存储无缝地结合起来,其性能类似于如果你实际上有太字节的RAM,你会得到的,并且UIImage可以检测到低内存情况,并且在没有任何代码的情况下释放几乎所有内存的RAM - 如果它知道图像最初加载的URL(效果更好)对于file:// URL而不是http:// URL)。

如果您可以使用UIImageNSData执行所需操作,那么您应该这样做。如果您具有无法实现的功能,则只使用较低级别的API。