เหตุใด UIBezierPath จึงเร็วกว่า Core Graphics path


90

ฉันกำลังเล่นกับเส้นทางการวาดและฉันสังเกตเห็นว่าอย่างน้อยในบางกรณี 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 () ดังนั้นฉันจึงสร้างวิธีการแยกกันเพื่อขีดเส้นแต่ละเส้นทางเพื่อที่ฉันจะได้เห็นเวลาที่แต่ละเส้นทางใช้ในเครื่องมือ ด้านล่างนี้คือผลลัพธ์ทั่วไป (เรียกต้นไม้กลับหัว); คุณจะเห็นว่า-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
UIBezierPathCGPathRefเป็นเสื้อคลุมรอบ จะเกิดอะไรขึ้นถ้าคุณเรียกใช้ทั้งสองอย่างสมมติว่าสิบล้านครั้งแล้วหาค่าเฉลี่ย แต่อย่าใช้เครื่องมือ แต่เป็นNSDateวัตถุสองชิ้นก่อนและหลังการดำเนินการ

1
@WTP ผลลัพธ์จะสอดคล้องกันบนอุปกรณ์และในโปรแกรมจำลองและไม่เปลี่ยนแปลงไม่ว่า-drawRect:จะเรียกว่ากี่สิบครั้งหรือสองสามร้อย ฉันได้ลองใช้กับส่วนโค้งมากถึง 80000 ในเครื่องจำลอง (มากเกินไปสำหรับอุปกรณ์) ผลลัพธ์จะเหมือนกันเสมอ: CGPath ใช้เวลาประมาณสองเท่าของ UIBezierPath แม้ว่าทั้งคู่จะใช้ CGContextStrokePath () ในการวาด ดูเหมือนชัดเจนว่าเส้นทางที่ UIBezierPath สร้างขึ้นนั้นมีประสิทธิภาพมากกว่าเส้นทางที่ฉันสร้างด้วย CGPathAddCurveToPoint () ฉันต้องการทราบวิธีสร้างเส้นทางที่มีประสิทธิภาพเช่น UIBezierPath
Caleb

คำตอบ:


156

คุณถูกต้องในนั้นUIBezierPathเป็นเพียงตัวห่อวัตถุประสงค์ -c สำหรับ Core Graphics ดังนั้นจึงจะทำงานเปรียบเทียบกันได้ ความแตกต่าง (และเหตุผลสำหรับเดลต้าประสิทธิภาพของคุณ) เป็นของCGContextรัฐเมื่อวาดของคุณโดยตรงค่อนข้างแตกต่างในการติดตั้งว่าโดยCGPath 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];
}

ภาพรวมจากเครื่องมือ: ภาพรวมของเครื่องมือแสดงประสิทธิภาพที่เท่าเทียมกัน


6
ขอขอบคุณที่สละเวลาตรวจสอบและเขียนคำอธิบายที่ชัดเจนเช่นนี้ นี่คือคำตอบที่ยอดเยี่ยมจริงๆ
Caleb

14
+1 สำหรับไอคอน atari bruce lee ... และอาจเป็นคำตอบ
Grady Player

5
ดังนั้น ... ความแตกต่างของประสิทธิภาพ 2x คือการตั้งค่า cgcontext อย่างน้อยหนึ่งอย่างเช่น: "lineWidth of 2.0 ทำงานได้แย่กว่า lineWidth ที่ 1.0" ... ?
อดัม

2
FWIW ฉันพบว่าความกว้างของเส้น 1.0 จะเร็วที่สุดเสมอ - สมมติฐานของฉันคือเพราะการลดกลายเป็นปัญหาสำหรับความกว้าง> 1px
Mark Aufflick
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.