为什么UIBezierPath比Core Graphics path更快?

92

我在尝试绘制路径时发现,在至少某些情况下,UIBezierPath的性能优于我原本认为与之等效的Core Graphics。下面的-drawRect:方法创建了两个路径:一个是UIBezierPath,另一个是CGPath。这两个路径除了位置不同以外完全相同,但描边CGPath要花费大约两倍的时间比描边UIBezierPath。

- (void)drawRect:(CGRect)rect
{
    CGContextRef ctx = UIGraphicsGetCurrentContext();

    // Create the two paths, cgpath and uipath.
    CGMutablePathRef cgpath = CGPathCreateMutable();
    CGPathMoveToPoint(cgpath, NULL, 0, 100);

    UIBezierPath *uipath = [[UIBezierPath alloc] init];
    [uipath moveToPoint:CGPointMake(0, 200)];

    // Add 200 curve segments to each path.
    int iterations = 200;
    CGFloat cgBaseline = 100;
    CGFloat uiBaseline = 200;
    CGFloat xincrement = self.bounds.size.width / iterations;
    for (CGFloat x1 = 0, x2 = xincrement;
         x2 < self.bounds.size.width;
         x1 = x2, x2 += xincrement)
    {
        CGPathAddCurveToPoint(cgpath, NULL, x1, cgBaseline-50, x2, cgBaseline+50, x2, cgBaseline);
        [uipath addCurveToPoint:CGPointMake(x2, uiBaseline)
                  controlPoint1:CGPointMake(x1, uiBaseline-50)
                  controlPoint2:CGPointMake(x2, uiBaseline+50)];
    }
    [[UIColor blackColor] setStroke];
    CGContextAddPath(ctx, cgpath);

    // Stroke each path.
    [self strokeContext:ctx];
    [self strokeUIBezierPath:uipath];

    [uipath release];
    CGPathRelease(cgpath);
}

- (void)strokeContext:(CGContextRef)context
{
    CGContextStrokePath(context);
}

- (void)strokeUIBezierPath:(UIBezierPath*)path
{
    [path stroke];
}

两个路径都使用了CGContextStrokePath()函数,因此我创建了单独的方法来描绘每个路径,以便我可以在Instruments中查看每个路径所用的时间。以下是典型结果(调用树倒置); 您可以看到-strokeContext:需要9.5秒,而-strokeUIBezierPath:仅需要5秒。

Running (Self)      Symbol Name
14638.0ms   88.2%               CGContextStrokePath
9587.0ms   57.8%                 -[QuartzTestView strokeContext:]
5051.0ms   30.4%                 -[UIBezierPath stroke]
5051.0ms   30.4%                  -[QuartzTestView strokeUIBezierPath:]

看起来UIBezierPath以某种方式优化了它创建的路径,或者我用一种天真的方式创建了CGPath。我该怎么做才能加快我的CGPath绘制速度?


2
+1,听起来不符合直觉。 - Grady Player
1
我发现在绘制线条、路径等方面,CoreGraphics通常非常慢。我不知道为什么,但我大多数情况下必须使用OpenGL或Cocos2D进行高效绘图。当然,我明白它更快,但我真的不明白为什么CG如此之慢,考虑到它应该正在使用OpenGL本身。 - Accatyyc
4
UIBezierPath 是对 CGPathRef 的封装。如果你运行这两个的代码,比如说一千万次,然后取平均值,但不使用 Instruments 工具,而是用两个 NSDate 对象记录操作前后的时间。 - user142019
1
@WTP,在设备和模拟器上,结果始终如一,无论 -drawRect: 被调用几十次还是几百次都不会改变。我已经在模拟器上尝试了多达 80000 条曲线段(对于设备来说太多了)。结果总是差不多:CGPath 的时间大约是 UIBezierPath 的两倍,尽管两者都使用 CGContextStrokePath() 进行绘制。很明显,UIBezierPath 构造的路径比我使用 CGPathAddCurveToPoint() 创建的路径更有效率。我想知道如何像 UIBezierPath 一样构建高效的路径。 - Caleb
1个回答

159

您说的没错,UIBezierPath只是Core Graphics的Objective-C封装,因此性能会相当。造成性能差异(以及原因)的是在直接绘制CGPath时,CGContext状态与使用UIBezierPath时设置的状态非常不同。如果您查看UIBezierPath,它有以下设置:

  • lineWidth,
  • lineJoinStyle,
  • lineCapStyle,
  • miterLimit
  • flatness

当检查[path stroke]的调用(反汇编),您将注意到它在执行CGContextStrokePath之前基于这些先前值配置当前图形上下文。如果在绘制CGPath之前执行相同的操作,则性能将相同:

- (void)drawRect:(CGRect)rect
{
    CGContextRef ctx = UIGraphicsGetCurrentContext();

    // Create the two paths, cgpath and uipath.
    CGMutablePathRef cgpath = CGPathCreateMutable();
    CGPathMoveToPoint(cgpath, NULL, 0, 100);

    UIBezierPath *uipath = [[UIBezierPath alloc] init];
    [uipath moveToPoint:CGPointMake(0, 200)];

    // Add 200 curve segments to each path.
    int iterations = 80000;
    CGFloat cgBaseline = 100;
    CGFloat uiBaseline = 200;
    CGFloat xincrement = self.bounds.size.width / iterations;
    for (CGFloat x1 = 0, x2 = xincrement;
         x2 < self.bounds.size.width;
         x1 = x2, x2 += xincrement)
    {
        CGPathAddCurveToPoint(cgpath, NULL, x1, cgBaseline-50, x2, cgBaseline+50, x2, cgBaseline);
        [uipath addCurveToPoint:CGPointMake(x2, uiBaseline)
                  controlPoint1:CGPointMake(x1, uiBaseline-50)
                  controlPoint2:CGPointMake(x2, uiBaseline+50)];
    }
    [[UIColor blackColor] setStroke];
    CGContextAddPath(ctx, cgpath);

    // Stroke each path
    CGContextSaveGState(ctx); {
        // configure context the same as uipath
        CGContextSetLineWidth(ctx, uipath.lineWidth);
        CGContextSetLineJoin(ctx, uipath.lineJoinStyle);
        CGContextSetLineCap(ctx, uipath.lineCapStyle);
        CGContextSetMiterLimit(ctx, uipath.miterLimit);
        CGContextSetFlatness(ctx, uipath.flatness);
        [self strokeContext:ctx];
        CGContextRestoreGState(ctx);
    }
    [self strokeUIBezierPath:uipath];

    [uipath release];
    CGPathRelease(cgpath);
}

- (void)strokeContext:(CGContextRef)context
{
    CGContextStrokePath(context);
}

- (void)strokeUIBezierPath:(UIBezierPath*)path
{
    [path stroke];
}

Instruments的快照: Instruments快照显示性能相等


6
感谢您抽出时间查看并写下如此清晰的解释。这确实是一个很好的答案。 - Caleb
14
+1表示对Atari Bruce Lee图标的赞同,可能还有对回答的支持。 - Grady Player
5
那么,两倍的性能差异可能是由cgcontext设置中的一个或多个因素造成的——例如,像“线宽为2.0比线宽为1.0表现更差”这样的情况……? - Adam
2
就我个人而言,我一直发现线宽为1.0是最快的——我的假设是因为斜接对于大于1像素的宽度成为了一个问题。 - Mark

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接