2012-01-03 12 views
13

Estoy trabajando en una aplicación de iOS que visualiza los datos como un gráfico de líneas. El gráfico se dibuja como CGPath en una pantalla completa personalizada UIView y contiene como máximo 320 puntos de datos. Los datos se actualizan con frecuencia y el gráfico debe volverse a dibujar en consecuencia; una frecuencia de actualización de 10 por segundo sería agradable.Rendimiento al dibujar CGPaths con frecuencia

Hasta ahora, tan fácil. Parece, sin embargo, que mi enfoque requiere mucho tiempo de CPU. Actualizar el gráfico con 320 segmentos a 10 veces por segundo da como resultado una carga de CPU del 45% para el proceso en un iPhone 4S.

Tal vez subestimo el trabajo de gráficos bajo el capó, pero para mí la carga de la CPU parece mucho para esa tarea.

A continuación se muestra la función drawRect() que recibe llamadas cada vez que un nuevo conjunto de datos está listo. N tiene el número de puntos y points es un vector CGPoint* con las coordenadas para dibujar.

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

he intentado hacer que la ruta de acceso a una línea CGContext primero antes de añadir a la capa actual como se sugiere here, pero sin ningún resultado positivo. También jugueteé con un dibujo de aproximación al CALayer directamente, pero eso tampoco importó.

¿Alguna sugerencia de cómo mejorar el rendimiento para esta tarea? ¿O es que la representación simplemente más trabajo para la CPU que me doy cuenta? ¿OpenGL tendría algún sentido/diferencia?

Gracias/Andi

Actualización: también trató de usar UIBezierPath en lugar de CGPath. Esta publicación here da una buena explicación de por qué eso no ayudó. Afinando CGContextSetMiterLimit y col. tampoco trajo un gran alivio.

Actualización n. ° 2: Finalmente cambié a OpenGL. Fue una curva de aprendizaje empinada y frustrante, pero el aumento en el rendimiento es simplemente increíble. Sin embargo, los algoritmos anti-aliasing de CoreGraphics hacen un trabajo mejor que el que se puede lograr con 4x-multisampling en OpenGL.

+0

Su color es una constante. Muévelo si drawRect y sigue reutilizándolo en lugar de pedir uno nuevo cada vez. Lo mismo para el camino. Como StrokePath() "vacía la ruta", puede reutilizar el mismo objeto de ruta una y otra vez. ¿Qué cambia eso? – verec

+0

La documentación dice que la ruta está vacía, pero al menos en mi código no lo está. Simplemente sigue creciendo. En cuanto al color, tienes un punto. Eso fue malo, pero no la solución. Gracias. –

+0

Te refieres a UIBezierPath, ¿verdad? NSBezierPath solo existe en la Mac. –

Respuesta

7

Este post here da una buena explicación de por qué eso no ayudó.

También explica por qué su método drawRect: es lento.

Está creando un objeto CGPath cada vez que dibuja. No necesitas hacer eso; solo necesita crear un nuevo objeto CGPath cada vez que modifique el conjunto de puntos. Mueva la creación de CGPath a un nuevo método al que llame solo cuando cambie el conjunto de puntos, y mantenga el objeto CGPath entre llamadas a ese método. Tenga drawRect: simplemente recupérelo.

Ya descubriste que la renderización es lo más caro que estás haciendo, lo cual es bueno: no puedes hacer el procesamiento más rápido, ¿o sí? De hecho, drawRect: idealmente debe hacer nada más que renderizado, por lo que su objetivo debe ser lograr que el tiempo de renderización sea lo más parecido posible al 100%, lo que significa mover todo lo demás, tanto como sea posible, del código de dibujo.

+0

Peter, ¡gracias por esta respuesta! Entiendo que renderizar es lo que le pido a la CPU que haga y lo que está haciendo. Solo me sorprendió ocuparlo, esto es mucho. Creo que le daré una oportunidad a OpenGL y veré si puede ayudarme a descargar algo de trabajo en la GPU. –

+0

Tengo el mismo problema ... y tengo que crear un nuevo CGPath cada vez que aparece un nuevo punto ... y eso es 128 veces por segundo –

0

¿Has probado usar UIBezierPath? UIBezierPath utiliza CGPath bajo la cubierta, pero sería interesante ver si el rendimiento difiere por alguna razón sutil. De Apple's Documentation:

Para la creación de caminos en iOS, se recomienda que utilice UIBezierPath en lugar de funciones CGPath a menos que necesite algunas de las capacidades que sólo Core Graphics proporciona, tales como la adición de puntos suspensivos para caminos. Para más información sobre la creación y representación caminos en UIKit, consulte “Dibujo de formas El uso de Bezier Caminos.”

Me sería también intente configurar diferentes propiedades en el CGContext, en particular, la línea de combinación diferente estilos usando CGContextSetLineJoin(), a ver si eso hace alguna diferencia.

¿Ha perfilado su código utilizando el instrumento Time Profiler en Instruments? Esa es probablemente la mejor manera de descubrir dónde se está produciendo realmente el cuello de botella de rendimiento, incluso cuando el cuello de botella se encuentre en alguna parte dentro de los marcos del sistema.

+0

I actualicé mi publicación con un comentario sobre NSBezierPath y modificando los parámetros de Line. Time Profiler muestra que la mayoría de las calorías (83%) se están quemando dentro de aa_render, que es parte de CG –

+0

¿Qué pasa con los perfiles utilizando Instruments? ¿Alguna idea? –

+0

(Disculpe, Pulso retorno después de la primera oración;)) –

0

No soy experto en esto, pero lo que dudaría primero es que podría tomarse un tiempo actualizar los 'puntos' en lugar de renderizarse. En este caso, simplemente podría dejar de actualizar los puntos y repetir el procesamiento de la misma ruta, y ver si le lleva casi el mismo tiempo de CPU. De lo contrario, puede mejorar el rendimiento centrándose en el algoritmo de actualización.

Si realmente es el problema de la representación, creo que OpenGL ciertamente debería mejorar el rendimiento ya que en teoría representará las 320 líneas al mismo tiempo.

+0

La actualización de los datos no es el problema (lo compruebo en Time Profiler). También evité repetir el mismo camino una y otra vez, ya que en la vida real los datos también están cambiando. Y no quería facilitar la vida de CG simplemente actualizando el mismo mapa de bits;) En este punto, estoy luchando para evitar OpenGL, pero si tengo que ... –

4

Dependiendo de cómo haga su ruta, puede ser que dibujar 300 rutas separadas sea más rápido que una ruta con 300 puntos. La razón de esto es que a menudo el algoritmo de dibujo buscará descubrir líneas superpuestas y cómo hacer que las intersecciones se vean "perfectas", cuando quizás solo desee que las líneas se solapen opacamente entre sí. Muchos algoritmos de superposición e intersección son N ** 2 o más en complejidad, por lo que la velocidad de dibujo se escala con el cuadrado de la cantidad de puntos en una ruta.

Depende de las opciones exactas (algunas de ellas predeterminadas) que utiliza. Tienes que intentarlo.

0

He hecho esto usando Metal en mi proyecto. Obviamente, existe una complejidad adicional en el uso del metal, por lo que, dependiendo de sus requisitos de rendimiento, puede o no ser adecuado.

Con Metal, todo el trabajo se realiza en la GPU y el uso de la CPU será casi cero. En la mayoría de los dispositivos iOS puedes renderizar varios cientos o varios miles de curvas sin problemas, a 60 FPS.

He aquí algunos ejemplos de código que escribí, podría proporcionar un buen punto de partida para alguien: https://github.com/eldade/ios_metal_bezier_renderer

Cuestiones relacionadas