【问题标题】:Performance when frequently drawing CGPaths频繁绘制 CGPath 时的性能
【发布时间】:2012-01-03 16:57:59
【问题描述】:

我正在开发一个将数据可视化为折线图的 iOS 应用程序。该图在全屏自定义UIView 中绘制为CGPath,最多包含320 个数据点。数据经常更新,需要相应地重新绘制图表——10/秒的刷新率会很好。

到目前为止很容易。然而,我的方法似乎需要大量的 CPU 时间。以每秒 10 次的速度刷新包含 320 个段的图表会导致 iPhone 4S 上进程的 CPU 负载为 45%。

也许我低估了引擎盖下的图形工作,但对我来说,该任务的 CPU 负载似乎很大。

下面是我的drawRect() 函数,每次准备好一组新数据时都会调用它。 N 保存点数,points 是一个 CGPoint* 向量,其中包含要绘制的坐标。

- (void)drawRect:(CGRect)rect {

    CGContextRef context = UIGraphicsGetCurrentContext();

    // set attributes
    CGContextSetStrokeColorWithColor(context, [UIColor lightGrayColor].CGColor);
    CGContextSetLineWidth(context, 1.f);

    // create path
    CGMutablePathRef path = CGPathCreateMutable();
    CGPathAddLines(path, NULL, points, N+1);

    // stroke path
    CGContextAddPath(context, path);
    CGContextStrokePath(context);

    // clean up
    CGPathRelease(path); 
}

我尝试先渲染离线 CGContext 的路径,然后按照建议 here 将其添加到当前层,但没有任何积极的结果。我还摆弄了一种直接绘制到 CALayer 的方法,但这也没什么区别。

有什么建议可以提高这项任务的性能吗?还是我意识到,渲染对 CPU 的工作量更大? OpenGL 有什么意义/区别吗?

谢谢/安迪

更新:我也尝试使用UIBezierPath 而不是CGPath。这篇文章here 很好地解释了为什么这没有帮助。调整CGContextSetMiterLimit 等。也没有带来很大的缓解。

更新 #2:我最终切换到了 OpenGL。这是一个陡峭而令人沮丧的学习曲线,但性能提升令人难以置信。但是,CoreGraphics 的抗锯齿算法比 OpenGL 中的 4 倍多重采样做得更好。

【问题讨论】:

  • 你的颜色是一个常数。如果 drawRect 将其移出并继续重用它,而不是每次都要求一个新的。路径同上。由于 StrokePath() “清空路径”,您可以一遍又一遍地重用相同的路径对象。这有什么变化?
  • 文档声称路径已清空,但至少在我的代码中没有。它只是不断增长。至于颜色,你有道理。那很糟糕,但不是解决方案。谢谢。
  • 你的意思是 UIBezierPath,对吧? NSBezierPath 仅存在于 Mac 上。
  • 我愿意。谢谢。相应地编辑了帖子。
  • 这里有同样的问题。在我看来,Apple 在 iOS 上的 CGPath 实现非常缓慢 - 一旦超过 20-30 条路径,性能就会直线下降......完全没有理由。相同的硬件,相同的数据集,如果我在 OpenGL 中进行粗制滥造的重新实现,我将获得 20 倍的性能。我不知道苹果在做什么,但这似乎是非常错误的:(。

标签: objective-c ios core-graphics cgpath


【解决方案1】:

这篇帖子here 很好地解释了为什么这没有帮助。

这也解释了为什么你的drawRect: 方法很慢。

每次绘制时都会创建一个 CGPath 对象。你不需要这样做;您只需在每次修改点集时创建一个新的 CGPath 对象。将 CGPath 的创建移至仅在点集更改时调用的新方法,并在对该方法的调用之间保留 CGPath 对象。让drawRect: 简单地检索它。

您已经发现渲染是您正在做的最昂贵的事情,这很好:您不能让渲染更快,是吗?实际上,drawRect: 理想情况下应该只做渲染,所以你的目标应该是尽可能地将渲染时间接近 100%——这意味着尽可能多地移动其他所有内容绘图代码。

【讨论】:

  • 彼得,感谢您的回答!我了解渲染是我要求 CPU 执行的操作以及它正在执行的操作。我只是对占据它这个感到惊讶。我想,我会给 OpenGL 一个机会,看看它是否可以帮助我将一些工作转移到 GPU 上。
  • 我有同样的问题..每次有新点进入时我都必须创建一个新的 CGPath.. 每秒 128 次
【解决方案2】:

根据您制作路径的方式,绘制 300 条单独的路径可能比绘制 300 个点的路径更快。这样做的原因是绘图算法通常会寻找重叠线以及如何使交叉点看起来“完美” - 当您可能只希望线不透明地相互重叠时。很多重叠和交叉算法的复杂度都在N**2左右,所以绘制速度与一条路径中点数的平方成正比。

这取决于您使用的确切选项(其中一些是默认选项)。你需要尝试一下。

【讨论】:

  • 这是一个惊人的观察结果。我正在绘制一个 UIBezierPath 来绘制约 4 个折线图,每个折线图有 20000 个点,大约需要 4 秒。通过分成 20000 个贝塞尔路径,它下降到 ~0.1 秒
【解决方案3】:

tl;dr:您可以设置底层CALayerdrawsAsynchronously 属性,您的CoreGraphics 调用将使用GPU 进行渲染。

CoreGraphics 中有一种方法可以控制渲染策略。默认情况下,所有 CG 调用都是通过 CPU 渲染完成的,这对于较小的操作来说很好,但对于较大的渲染作业来说效率非常低。

在这种情况下,只需设置底层CALayerdrawsAsynchronously 属性即可将CoreGraphics 渲染引擎切换到基于GPU、Metal 的渲染器,从而大大提高性能。在 macOS 和 iOS 上都是如此。

我进行了一些性能比较(涉及几个不同的 CG 调用,包括 CGContextDrawRadialGradientCGContextStrokePath 和使用 CTFrameDraw 的 CoreText 渲染),对于更大的渲染目标,性能大幅提升了 10 倍以上。

正如所料,随着渲染目标的缩小,GPU 的优势逐渐减弱,直到某个时候(通常对于小于 100x100 左右像素的渲染目标),CPU 实际上实现了比 GPU 更高的帧速率。 YMMV,当然这将取决于 CPU/GPU 架构等。

【讨论】:

  • CALayers 根据定义存在于 GPU 上,因此“使用 GPU 进行渲染”。 CALayers 本质上是 GPU 纹理之上的抽象。所以我很好奇在哪里记录了当 drawsAsynchronously 为 false 时它们是由 CPU 绘制的?
  • @JohnScalo 这更像是一种观察,而不是记录在案的行为。 CALayers 确实存在于 GPU 上,但 CoreGraphics 调用不存在。默认情况下,它们在 CPU 上呈现。根据我的经验,如果您在 CALayer 上设置 drawsAsynchronously 标志,它将返回一个 CGContext,让 CoreGraphics 在 GPU 上渲染(当它调用您的 renderInContext 时:)。
【解决方案4】:

您是否尝试过使用 UIBezierPath 代替? UIBezierPath 在底层使用 CGPath,但看看性能是否因一些微妙的原因而不同会很有趣。来自Apple's Documentation

iOS中创建路径,推荐使用UIBezierPath 而不是 CGPath 函数,除非您需要某些功能 只有 Core Graphics 提供,例如向路径添加椭圆。 有关在 UIKit 中创建和渲染路径的更多信息,请参阅“绘制形状 使用贝塞尔路径。”

我还会尝试在 CGContext 上设置不同的属性,特别是使用 CGContextSetLineJoin() 的不同的线连接样式,看看是否有什么不同。

您是否使用 Instruments 中的 Time Profiler 工具分析了您的代码?这可能是找到性能瓶颈实际发生位置的最佳方法,即使瓶颈位于系统框架内部。

【讨论】:

  • 我更新了我的帖子,添加了关于 NSBezierPath 的评论并调整了 Line 参数。 Time Profiler 显示大部分卡路里 (83%) 都在 CG 的一部分 aa_render 中燃烧。
  • (对不起,我在第一句话后按了回车;))
【解决方案5】:

我不是这方面的专家,但我首先要怀疑的是,更新“点”而不是渲染本身可能需要时间。在这种情况下,您可以简单地停止更新点并重复渲染相同的路径,看看它是否需要几乎相同的 CPU 时间。如果没有,您可以专注于更新算法来提高性能。

如果真的是渲染的问题,我认为OpenGL肯定会提高性能,因为理论上它会同时渲染所有320行。

【讨论】:

  • 更新数据不是问题(我在 Time Profiler 中检查过)。我还避免一遍又一遍地渲染相同的路径,因为在现实生活中数据也在变化。而且我不想通过更新同一个位图来让 CG 的生活变得轻松;)在这一点上,我正在努力避免使用 OpenGL,但如果我不得不......
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2019-09-04
  • 2014-02-11
  • 2012-08-02
  • 1970-01-01
  • 1970-01-01
  • 2012-08-13
  • 2011-05-21
相关资源
最近更新 更多