我的类(class)正在屏幕外渲染图像。我认为重用 CGContext
而不是一次又一次地为每个图像创建相同的上下文将是一件好事。我设置了一个成员变量 _imageContext
,所以如果 _imageContext
为 nil,我只需要创建一个新的上下文,如下所示:
if(!_imageContext)
_imageContext = [self contextOfSize:imageSize];
代替:
CGContextRef imageContext = [self contextOfSize:imageSize];
当然我不再发布CGContext
了。
这些是我所做的唯一更改,事实证明,重用上下文将渲染速度从大约 10 毫秒减慢到 60 毫秒。我错过了什么吗?在再次进入之前我是否必须清除上下文或其他内容?还是为每个图像重新创建上下文的正确方法?
编辑
找到最奇怪的联系..
当我在寻找当应用程序开始呈现图像时应用程序的内存显着增加的原因时,我发现问题出在我将呈现的图像设置为 NSImageView
。
imageView.image = nil;
imageView.image = [[NSImage alloc] initWithCGImage:_imageRef size:size];
看起来 ARC 没有发布之前的 NSImage
。避免这种情况的第一种方法是将新图像绘制到旧图像中。
[imageView.image lockFocus];
[[[NSImage alloc] initWithCGImage:_imageRef size:size] drawInRect:NSMakeRect(0, 0, size.width, size.height) fromRect:NSZeroRect operation:NSCompositeSourceOver fraction:1.0];
[imageView.image unlockFocus];
[imageView setNeedsDisplay];
内存问题消失了,CGContext
- 重用问题发生了什么?
不重用上下文现在需要 20 毫秒而不是 10 毫秒 - 当然绘制到图像中比仅仅设置它需要更长的时间。
重用上下文也需要 20 毫秒而不是 60 毫秒。但为什么?我看不出可能有任何联系,但我可以通过设置 NSImageView
的图像而不是绘制它来重现重复使用需要更多时间的旧状态。
最佳答案
我对此进行了调查,并观察到同样的减速。将 Instruments 设置为对内核调用和用户态调用进行采样显示了罪魁祸首。 @RyanArtecona 的评论是正确的。我在两次测试运行中将 Instruments 集中在最底部的用户区调用 CGSColorMaskCopyARGB8888_sse
(一次重用上下文,另一次每次都创建一个新上下文),然后反转生成的调用树。在不重用上下文的情况下,我看到最重的内核跟踪是:
Running Time Self Symbol Name
668.0ms 32.3% 668.0 __bzero
668.0ms 32.3% 0.0 vm_fault
668.0ms 32.3% 0.0 user_trap
668.0ms 32.3% 0.0 CGSColorMaskCopyARGB8888_sse
这是内核将因 CGSColorMaskCopyARGB8888_sse
访问而出现故障的内存页面归零。这意味着 CGContext 映射 VM 页面以支持位图上下文,但内核实际上并不执行与该操作相关的工作,直到有人实际访问该内存。实际映射/错误发生在第一次访问时。
现在让我们看看重用上下文时最重的内核跟踪:
Running Time Self Symbol Name
1327.0ms 35.0% 1327.0 bcopy
1327.0ms 35.0% 0.0 user_trap
1327.0ms 35.0% 0.0 CGSColorMaskCopyARGB8888_sse
这是内核复制页面。我的钱将用于提供@RyanArtecona 在他的评论中谈到的行为的底层写时复制机制:
In the Apple docs for CGBitmapContextCreateImage, it says the actual bit-copying operation doesn't happen until more drawing is done on the original context.
在我用来测试的设计案例中,非重用案例的执行时间为 3392 毫秒,而重用案例的执行时间为 4693 毫秒(明显较慢)。仅考虑每种情况下最重的单个跟踪,内核跟踪表明我们在第一次访问时花费了 668.0ms 零填充新页面,在图像获得引用后的第一次写入中花费了 1327.0ms 写入写时复制页面到那些页面。这是 659 毫秒的差异。仅这一差异就占了两种情况之间差距的约 50%。
因此,简单地提炼一下,非重用上下文更快,因为当您创建上下文时,它知道页面是空的,并且没有其他人引用这些页面来强制复制它们你写信给他们。当您重用上下文时,页面会被其他人(您创建的图像)引用,并且必须在第一次写入时复制,以便在上下文状态发生变化时保留图像的状态。
您可以在调试器中单步执行时通过查看进程的虚拟内存映射来进一步探索这里发生的事情。 vmmap
是一个有用的工具。
实际上,您应该每次都创建一个新的 CGContext。
关于objective-c - 重用 CGContext 导致奇怪的性能损失,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/14071873/