performance ios optimization core-animation

performance - iOS/Core-Animation: ajuste de rendimiento



optimization (5)

¿Está usando sombras en su capa para mí? Fue un problema de rendimiento, entonces tiene 2 opciones. AFAIK: - configurando shadowPath de esa manera, CA no tiene que calcularlo cada vez - eliminando sombras y usando imágenes para reemplazar

Tengo mi aplicación funcionando en mi iPad. pero está funcionando muy mal, me estoy poniendo por debajo de 15 fps. ¿Alguien puede ayudarme a optimizar?

Es básicamente una rueda (derivada de UIView) que contiene 12 botones (derivada de UIControl).

A medida que el usuario lo gira, los botones se expanden y contraen dinámicamente (por ejemplo, el que está en la posición de las 12 en punto siempre debe ser el más grande)

Así que mi rueda contiene un:

- (void) displayLinkIsCallingBack: (CADisplayLink *) dispLink { : // using CATransaction like this goes from 14fps to 19fps [CATransaction begin]; [CATransaction setDisableActions: YES]; // NEG, as coord system is flipped/fucked self.transform = CGAffineTransformMakeRotation(-thetaWheel); [CATransaction commit]; if (BLA) [self rotateNotch: direction]; }

... que calcula a partir de las recientes entradas táctiles la nueva rotación de la rueda. Ya hay un problema de rendimiento que estoy siguiendo en un hilo aparte: iOS Core-Animation: problemas de rendimiento con las matrices de transformación CAT Interacción / Interpolación

Esta rutina también verifica si la rueda ha completado otra rotación de 1/12, y si es así le indica a los 12 botones que cambien de tamaño:

// Wheel.m - (void) rotateNotch: (int) direction { for (int i=0; i < [self buttonCount] ; i++) { CustomButton * b = (CustomButton *) [self.buttons objectAtIndex: i]; // Note that b.btnSize is a dynamic property which will calculate // the desired button size based on the button index and the wheels rotation. [b resize: b.btnSize]; } }

Ahora para el código de cambio de tamaño real, en button.m:

// Button.m - (void) scaleFrom: (float) s_old to: (float) s_new time: (float) t { CABasicAnimation * scaleAnimation = [CABasicAnimation animationWithKeyPath: @"transform.scale"]; [scaleAnimation setDuration: t ]; [scaleAnimation setFromValue: (id) [NSNumber numberWithDouble: s_old] ]; [scaleAnimation setToValue: (id) [NSNumber numberWithDouble: s_new] ]; [scaleAnimation setTimingFunction: [CAMediaTimingFunction functionWithName: kCAMediaTimingFunctionEaseOut] ]; [scaleAnimation setFillMode: kCAFillModeForwards]; scaleAnimation.removedOnCompletion = NO; [self.contentsLayer addAnimation: scaleAnimation forKey: @"transform.scale"]; if (self.displayShadow && self.shadowLayer) [self.shadowLayer addAnimation: scaleAnimation forKey: @"transform.scale"]; size = s_new; } // - - - - (void) resize: (float) newSize { [self scaleFrom: size to: newSize time: 1.]; }

Me pregunto si el problema está relacionado con la sobrecarga de múltiples operaciones de transformación y escala. La redistribución de cada botón requiere un segundo completo, y si estoy girando la rueda rápidamente, podría girar un par de revoluciones por segundo; eso significa que cada botón se redimensiona 24 veces por segundo.

** creando la capa del botón **

La última pieza del rompecabezas, supongo, es echar un vistazo a los contenidos del botón Capa. pero lo he intentado

contentsLayer.setRasterize = YES;

que efectivamente debería ser almacenado como un mapa de bits. por lo tanto, con la configuración, el código está en efecto redimensionando dinámicamente 12 mapas de bits.

No puedo creer que esto esté gravando el dispositivo más allá de sus límites. Sin embargo, el instrumento de animación principal me dice lo contrario; mientras estoy girando la rueda (arrastrando mi dedo en círculos), está reportando ~ 15 fps.

Esto no es bueno: eventualmente necesito colocar una capa de texto dentro de cada botón y esto va a reducir aún más el rendimiento (... a menos que esté usando la configuración .setRasterize anterior, en cuyo caso debería ser la misma).

¡Debe haber algo que estoy haciendo mal! ¿pero que?

EDITAR: aquí está el código responsable de generar la capa de contenido del botón (es decir, la forma con la sombra):

- (CALayer *) makeContentsLayer { CAShapeLayer * shapeOutline = [CAShapeLayer layer]; shapeOutline.path = self.pOutline; CALayer * contents = [CALayer layer]; // get the smallest rectangle centred on (0,0) that completely contains the button CGRect R = CGRectIntegral(CGPathGetPathBoundingBox(self.pOutline)); float xMax = MAX(abs(R.origin.x), abs(R.origin.x+R.size.width)); float yMax = MAX(abs(R.origin.y), abs(R.origin.y+R.size.height)); CGRect S = CGRectMake(-xMax, -yMax, 2*xMax, 2*yMax); contents.bounds = S; contents.shouldRasterize = YES; // try NO also switch (technique) { case kMethodMask: // clip contents layer by outline (outline centered on (0, 0)) contents.backgroundColor = self.clr; contents.mask = shapeOutline; break; case kMethodComposite: shapeOutline.fillColor = self.clr; [contents addSublayer: shapeOutline]; self.shapeLayer = shapeOutline; break; default: break; } if (NO) [self markPosition: CGPointZero onLayer: contents ]; //[self refreshTextLayer]; //[contents addSublayer: self.shapeLayerForText]; return contents; }

Como puede ver, estoy probando todos los enfoques posibles, estoy probando dos métodos para hacer la forma, y ​​por separado estoy alternando .should Rasterize

** comprometer el diseño de la interfaz de usuario para obtener una tasa de cuadros tolerable **

EDITAR: Ahora he intentado deshabilitar el comportamiento de cambio de tamaño dinámico hasta que la rueda se asienta en una nueva posición, y configuré wheel.setRasterize = YES. por lo tanto, está girando efectivamente una única UIView pre-rendida (que ciertamente ocupa la mayor parte de la pantalla) debajo del dedo (lo que hace feliz @ ~ 60fps), hasta que la rueda se detiene, momento en el que realiza esta animación de redimensionamiento lento ( @ <20fps).

Si bien esto da un resultado tolerable, parece absurdo que tenga que sacrificar mi diseño de interfaz de usuario de tal manera. Estoy seguro de que debo estar haciendo algo mal.

EDITAR: Acabo de intentar como experimento cambiar el tamaño de los botones manualmente; es decir, coloque una devolución de llamada de enlace de pantalla en cada botón, y calcule dinámicamente el tamaño esperado de este cuadro dado, deshabilite explícitamente las animaciones con CATransaction del mismo modo que hice con la rueda, establezca la nueva matriz de transformación (transformación de escala generada del tamaño esperado). añadido a esto, he establecido la capa de contenido de los botones shouldRasterize = YES. por lo tanto, debería ser simplemente escalar 12 mapas de bits en cada cuadro en una única vista de UIV que está girando. Sorprendentemente, esto es muy lento, incluso está parando el simulador. Definitivamente, es 10 veces más lento que hacerlo de forma automática utilizando la función de animación de animación central.


No tengo experiencia en el desarrollo de aplicaciones de iPad, pero sí tengo algunas en la optimización de videojuegos. Por lo tanto, no puedo dar una respuesta exacta, pero quiero dar algunos consejos sobre optimización.

No adivinar Perfil de ello.

Parece que está intentando realizar cambios sin perfilar el código. Cambiar algún código sospechoso y cruzar los dedos no funciona realmente. Debe crear un perfil de su código examinando cuánto long lleva cada tarea y con qué often deben ejecutarse. Intente desglosar sus tareas y coloque el código de perfil para medir el time y la frequency . Es incluso mejor si puede medir cuánta memory se utiliza para cada tarea o cuántos recursos del sistema. Encuentra tu cuello de botella basándose en una evidencia, no en tus sentimientos.

Para su problema específico, cree que el programa se vuelve muy lento cuando se inicia el trabajo de cambio de tamaño. Pero, ¿está seguro? Podría ser otra cosa. No lo sabemos con seguridad hasta que tengamos datos de perfiles reales.

Minimice el área problemática y mida el desempeño real antes de hacer cambios.

Después de perfilar, tienes un candidato para tu cuello de botella. Si aún puede dividir la causa en varias tareas pequeñas, hágalo y vaya a hacer un perfil de ellas hasta que ya no pueda dividirlas. Luego, intente medir su rendimiento preciso ejecutándolos repetidamente unas miles de veces. Esto es importante porque necesita conocer el rendimiento (velocidad y espacio) antes de realizar cambios para poder compararlo con cambios futuros.

Para su problema específico, si el cambio de tamaño es realmente el problema, intente examinar cómo funciona. ¿Cuánto tiempo se tarda en realizar una llamada de cambio de tamaño? ¿Con qué frecuencia necesita hacer un trabajo de cambio de tamaño para completar su trabajo?

Mejoralo. ¿Cómo? Depende.

Ahora, tienes el bloque de código problemático y su rendimiento actual. Ahora tienes que mejorarlo. ¿Cómo? Bueno, realmente depende de cuál sea el problema. podría buscar better algorithms , podría realizar la lazy fetching si puede retrasar los cálculos hasta que realmente necesite realizarlos, o podría hacer over-eager evaluation exagerada al almacenar algunos datos en caché si realiza la misma operación con demasiada frecuencia. Cuanto mejor conozca la causa, más fácil podrá mejorarla.

Para su problema específico, podría ser solo el límite de la función de la interfaz de usuario del sistema operativo. Por lo general, el botón de cambio de tamaño no es solo el botón de cambio de tamaño en sí, sino que también invalidates un área completa o su widget principal para que cada elemento de la interfaz de usuario se pueda representar correctamente. El botón de cambio de tamaño podría ser costoso y, si ese es el caso, podría resolver el problema simplemente usando un enfoque basado en imágenes en lugar de un sistema basado en la interfaz de usuario. Podría intentar usar OpenGL si las operaciones de imagen de la API del sistema operativo no son suficientes. Pero, nuevamente, no lo sabemos hasta que realmente los probamos y profile estas alternativas. ¡Buena suerte! :)


Probablemente debería volver a hacer esto en OpenGL


Pruébalo sin tus sombras y ve si eso mejora el rendimiento. Me imagino que lo mejorará mucho. Luego me gustaría usar la ruta de sombra de CALayer para representar sombras. Eso mejorará enormemente el rendimiento de la representación de sombra.

Los videos de animación principal de Apple de la WWDC del año pasado tienen mucha información sobre cómo aumentar el rendimiento en la animación principal.

Por cierto, estoy animando algo mucho más complejo que esto ahora mismo y funciona a la perfección incluso en un iPhone 3G más antiguo. El hardware / software es bastante capaz.


Sé que esta pregunta es antigua, pero todavía está actualizada. CoreAnimation (CA) es solo una envoltura alrededor de OpenGL , o mientras tanto tal vez alrededor de Metal . Las capas son, de hecho, texturas dibujadas en rectángulos y las animaciones se expresan mediante transformaciones 3D. Como todo esto es manejado por la GPU, debería ser ultra rápido ... pero no lo es. Todo el subsistema de CA parece bastante complejo y la traducción entre AppKit / UIKit y el mundo 3D es más difícil de lo que parece (si alguna vez intentaste escribir una envoltura de este tipo, sabes lo difícil que puede ser). Para el programador, CA ofrece una interfaz super simple de usar, pero esta simplicidad tiene un precio. Todos mis intentos de optimizar la CA muy lenta fueron hasta ahora inútiles; puede acelerarlo un poco, pero en algún momento tendrá que reconsiderar su enfoque: o CA es lo suficientemente rápido para hacer el trabajo por usted o necesita dejar de usar CA e implementar la animación por sí mismo usando el dibujo de vista clásico (si la CPU puede hacer frente a eso) o implementar las animaciones usted mismo usando una API 3D (entonces la GPU lo hará), en cuyo caso puede decidir cómo el mundo 3D interactúa con el resto de su aplicación; El precio es mucho más código para escribir o una API mucho más compleja de usar, pero los resultados hablarán por sí mismos al final.

Sin embargo, me gustaría dar algunos consejos genéricos sobre la aceleración de CA:

  • Cada vez que se "dibuja" en una capa o se carga contenido en una capa (una nueva imagen), los datos de la textura que respalda esta capa deben actualizarse. Todo programador 3D sabe: actualizar texturas es muy costoso . Evita eso a toda costa.
  • No use capas enormes, ya que las capas son demasiado grandes para ser manejadas directamente por la GPU como una sola textura, se dividen en múltiples texturas y esto solo empeora el rendimiento.
  • No use demasiadas capas ya que la cantidad de memoria que las GPU pueden gastar en texturas a menudo es limitada. Si necesita más memoria que ese límite, las texturas se intercambian (se eliminan de la memoria de la GPU para dejar espacio para otras texturas, y luego se agregan cuando es necesario dibujarlas). Vea el primer consejo anterior, este tipo de intercambio es muy costoso .
  • No vuelva a dibujar las cosas que no necesitan volver a dibujar, caché en imágenes en su lugar. Por ejemplo, dibujar sombras y dibujar gradientes son muy caros y, por lo general, rara vez cambian. Entonces, en lugar de hacer que CA los dibuje cada vez, dibujarlos una vez en una capa o dibujarlos en una imagen y cargar esa imagen en un CALayer , luego colocar la capa donde la necesite. Supervise cuándo necesita actualizarlos (por ejemplo, si el tamaño de un objeto ha cambiado), luego vuelva a dibujarlos una vez y, de nuevo, almacene en caché el resultado. CA también trata de almacenar en caché los resultados, pero obtienes mejores resultados si controlas el almacenamiento en caché.
  • Cuidado con la transparencia. Dibujar una capa opaca es siempre más rápido que dibujar una capa que no lo es. Así que evite usar la transparencia donde no sea necesaria, ya que el sistema no escaneará todo su contenido en busca de transparencia (o la falta de ella). Si un NSView no contiene un área en la que su padre brille a través, haga una subclase personalizada y anule isOpaque para devolver YES . Lo mismo se aplica a UIView s y capas donde ni el padre ni sus hermanos brillarán nunca, pero aquí es suficiente establecer la propiedad opaque en YES .

Si nada de eso realmente ayuda, está presionando a CA para que limite y probablemente necesite reemplazarlo con otra cosa.