Perché UIBezierPath è più veloce del percorso Core Graphics?


90

Stavo giocando con i tracciati e ho notato che in almeno alcuni casi, UIBezierPath supera quello che pensavo sarebbe stato un equivalente di Core Graphics. Il -drawRect:metodo seguente crea due percorsi: uno UIBezierPath e uno CGPath. I percorsi sono identici tranne che per le loro posizioni, ma accarezzare il CGPath richiede circa il doppio del tempo per accarezzare l'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];
}

Entrambi i percorsi utilizzano CGContextStrokePath (), quindi ho creato metodi separati per tracciare ogni percorso in modo da poter vedere il tempo utilizzato da ciascun percorso in Instruments. Di seguito sono riportati i risultati tipici (chiamata albero invertito); puoi vedere che -strokeContext:impiega 9,5 sec., mentre -strokeUIBezierPath:richiede solo 5 sec .:

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:]

Sembra che UIBezierPath stia in qualche modo ottimizzando il percorso che crea, oppure sto creando il CGPath in modo ingenuo. Cosa posso fare per velocizzare il mio disegno CGPath?


2
+1 che suona controintuitivo.
Grady Player

1
In genere ho trovato CoreGraphics molto lento nel disegnare linee, percorsi ecc. Non ho idea del perché, ma per lo più devo andare su OpenGL o usare Cocos2D per disegnare in modo efficiente. Certo, capisco che è più veloce, ma non capisco davvero perché CG sia molto più lento, considerando che dovrebbe usare OpenGL stesso.
Accatyyc

4
UIBezierPathè un involucro intorno CGPathRef. E se si eseguissero entrambi diciamo, dieci milioni di volte, quindi si prenda la media, ma non si utilizzino gli strumenti ma due NSDateoggetti prima e dopo le operazioni.

1
@WTP, i risultati sono coerenti sul dispositivo e nel simulatore e non cambiano se -drawRect:viene chiamato poche dozzine di volte o poche centinaia. L'ho provato con fino a 80000 segmenti di curva sul simulatore (troppi per il dispositivo). I risultati sono sempre gli stessi: CGPath impiega circa il doppio del tempo di UIBezierPath, anche se entrambi utilizzano CGContextStrokePath () per disegnare. Sembra chiaro che il percorso che UIBezierPath costruisce è in qualche modo più efficiente di quello che creo con CGPathAddCurveToPoint (). Mi piacerebbe sapere come costruire percorsi efficienti come fa UIBezierPath.
Caleb

Risposte:


154

Hai ragione in questo UIBezierPathè semplicemente un wrapper obiettivo-c per Core Graphics, e quindi funzionerà in modo comparabile. La differenza (e il motivo del delta delle prestazioni) è il tuo CGContextstato quando disegni il tuoCGPath direttamente è molto diverso da quello impostato da UIBezierPath. Se guardi UIBezierPath, ha impostazioni per:

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

Quando si esamina la chiamata (disassemblaggio) a [path stroke], si noterà che configura il contesto grafico corrente in base a quei valori precedenti prima di eseguire la CGContextStrokePathchiamata. Se fai lo stesso prima di disegnare il tuo CGPath, eseguirà lo stesso:

- (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];
}

Istantanea dagli strumenti: Istantanea degli strumenti che mostra le stesse prestazioni


6
Grazie per aver dedicato del tempo per esaminare questo e scrivere una spiegazione così chiara. Questa è davvero un'ottima risposta.
Caleb

14
+1 per l'icona Atari Bruce Lee ... e forse per la risposta.
Grady Player

5
Quindi ... la differenza di prestazioni 2x era una o più delle impostazioni di cgcontext - ad es. Forse qualcosa come: "lineWidth di 2.0 ha prestazioni peggiori di lineWidth di 1.0" ...?
Adam

2
FWIW Ho sempre trovato la larghezza della linea 1.0 per essere la più veloce - la mia ipotesi è che il taglio obliquo diventa un problema per larghezze> 1px.
Mark Aufflick
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.