Gotta Go Fast: Building for Speed ​​en iOS. Parte 1



Hay muchos consejos y trucos que permiten a los desarrolladores de iOS saber cómo realizar optimizaciones de rendimiento para que las animaciones en las aplicaciones funcionen sin problemas. Después de leer el artículo, se dará cuenta de lo que significa 16.67 milisegundos para el desarrollador de iOS y qué herramientas son mejores para rastrear el código.

El artículo se basa en la charla magistral impartida por Luke Parham , actualmente ingeniero de iOS en Apple y autor de tutoriales para el desarrollo de iOS en RayWenderlich.com, en la Conferencia Internacional de Desarrolladores Móviles MBLT DEV 2017 .

"Hola chicos. Si puede, digamos, puede ahorrar 10 segundos del tiempo de arranque, multiplicar eso por 5 millones de usuarios y eso es 50 millones de segundos cada día. Más de un año, eso es probablemente decenas de vidas. Entonces, si haces que arranque diez segundos más rápido, has salvado una docena de vidas. Eso realmente vale la pena, ¿no te parece?

Steve Jobs sobre el rendimiento (tiempo de arranque de Apple II).

Rendimiento en iOS o cómo salir de main


El hilo principal es responsable de aceptar la entrada del usuario y mostrar los resultados en la pantalla. Aceptar grifos, sartenes, todos los gestos y luego renderizar. La mayoría de los teléfonos móviles modernos se procesan a 60 cuadros por segundo. Significa que todos quieren hacer todo el trabajo en 16.67 milisegundos. Entonces, salir del hilo principal es algo realmente grande.

Si algo demora más de 16.67 milisegundos, entonces soltará cuadros automáticamente y sus usuarios lo verán cuando haya animaciones. Algunos dispositivos tienen incluso menos tiempo para renderizar, por ejemplo, el nuevo iPad tiene 120 Hertz, por lo que solo hay 8 milisegundos por marco para hacer el trabajo.

Marcos caídos


Regla n. ° 1: utilice un CADisplayLink para rastrear fotogramas descartados


CADisplayLink es un temporizador especial que se activa en Vsync. El Vsync es cuando la aplicación se procesa en la pantalla, y ocurre cada 16 milisegundos. Para fines de prueba, en su AppDelegate, puede configurar CADisplayLink agregado al ciclo de ejecución principal y luego simplemente tener otra función donde hacer un poco de matemática. Luego realiza un seguimiento de cuánto tiempo ha estado funcionando la aplicación y cuánto tiempo ha pasado desde la última vez que se activó esta función. Y vea si tardó más de 16 milisegundos.



Esto solo se dispara cuando realmente se renderiza. Si estaba haciendo un montón de trabajo y redujo la velocidad del hilo principal, esto se ejecutará 100 milisegundos más tarde, lo que significa que ha hecho demasiado trabajo y ha perdido fotogramas en ese momento.

Por ejemplo, esta es la aplicación Catstagram. Tartamudea cuando se carga la imagen. Y luego puede ver que el marco se cayó en un momento determinado y tuvo un tiempo transcurrido de unos 200 milisegundos. Eso significa que esta aplicación está haciendo algo que lleva demasiado tiempo.



A los usuarios no les gusta tal experiencia, especialmente si la aplicación es compatible con dispositivos más antiguos como iPhone 5, iPods antiguos, etc.

Perfil de tiempo


Time Profiler es probablemente la herramienta más útil para rastrear las cosas. Las otras herramientas son útiles pero, al final, en Fyusion usamos Time Profiler como el 90% del tiempo. Los sospechosos habituales de la aplicación son scrollview, texto e imágenes.

Las imágenes son realmente grandes. Tenemos decodificación JPEG: "UIImageView" equivale a algo de UIImage. UIimages decodifica todos los archivos JPEG para la aplicación. Lo hacen lentamente, por lo que no puedes seguir el rendimiento directamente. No sucede justo cuando configura la imagen, pero puede verla en los rastros del generador de perfiles de tiempo.
La medición de texto es otra gran cosa. Aparece, por ejemplo, si tiene muchos tipos realmente complejos como el japonés o el chino. Estos pueden tomar mucho tiempo para medir las líneas.

El diseño de la jerarquía también ralentiza el procesamiento de la aplicación. Esto es especialmente cierto con el diseño automático. Es conveniente, pero también es agresivamente lento en comparación con el diseño manual. Por lo tanto, es una de esas compensaciones. Si ralentiza la aplicación, puede ser el momento de alejarse y probar alguna otra técnica de diseño.

Traza de ejemplo





En el árbol de llamadas de ejemplo, puede ver cuánto trabajo están haciendo sus CPU. Puede cambiar las vistas, mirarlo por hilos, mirarlo por CPU. Por lo general, lo más interesante es separar por hilos y luego mirar lo que está en main.

Muchas veces, cuando comienzas a mirar esto, parece súper abrumador. A veces tienes un sentimiento: “¿Qué es toda esta basura? No sé lo que esto significa "FRunLoopDoSource0".

Pero es una de las cosas en las que podría profundizar y comprender cómo funcionan las cosas y comienza a tener sentido. Para que pueda seguir el rastro de la pila y ver todas las cosas del sistema que no escribió. Pero en la parte inferior, puedes ver tu código real.

El árbol de llamadas


Por ejemplo, tenemos una aplicación realmente simple que tiene la función principal, y luego llama a algunos métodos dentro de la principal. Lo que hace el generador de perfiles de tiempo es que toma una instantánea de cualquier rastro de su pila en este momento por defecto cada milisegundo. Luego espera un milisegundo y toma una instantánea, donde ha llamado "principal", que ha llamado "foo", que ha llamado "bar". Hay el primer rastro de la pila sobre la captura de pantalla. Entonces eso se recoge. Tenemos estos recuentos: 1, 1, 1.



Cada una de estas funciones se ha llamado una vez. Luego, un milisegundo más tarde, capturamos otra pila. Y esta vez, es exactamente lo mismo, aumentamos todos los recuentos por 2.



Luego, en el tercer milisegundo, tenemos una pila de llamadas ligeramente diferente. Main llama directamente a "bar". Principal y bar están arriba por uno. Pero luego tenemos una división. A veces, las llamadas principales "foo", a veces las llamadas principales "bar" directamente. Eso pasa una vez. Un método ha sido llamado dentro de otro.

Más adelante, se ha llamado a un método dentro de otro que llama al tercer método. Vemos que "buz" fue llamado dos veces. Pero es un método tan pequeño que ocurre entre un milisegundo.

Usando el generador de perfiles de tiempo, es importante recordar que no proporciona los tiempos exactos. No dice exactamente cuánto tiempo lleva un método. Indica con qué frecuencia aparece en las instantáneas, que solo puede aproximarse a cuánto tiempo llevó la ejecución de cada método. Porque si algo es lo suficientemente corto, nunca aparecerá.



Si cambia al modo de consola en el árbol de llamadas, puede ver todos los eventos de caída de cuadros y puede hacerlos coincidir. Tenemos un montón de cuadros que se están cayendo y tenemos un montón de trabajo en curso. Puede ampliar el generador de perfiles de tiempo y ver lo que se estaba ejecutando solo en esta sección.



En realidad, en Mac, en general, puedes hacer clic en los triángulos de revelación y se abrirá mágicamente y te mostrará lo que sea más importante. Se desplegará a lo que sea que esté haciendo más trabajo. Y el 90% del tiempo será CFRunLoopRun, y luego las devoluciones de llamada.



Toda la aplicación se basa en un Run Loop. Tiene este bucle que dura para siempre y luego, en cada iteración del bucle, se llaman las devoluciones de llamada. Cuando llegue a este punto, puede profundizar en cada uno de estos y básicamente ver cuáles son sus tres o cuatro cuellos de botella principales.

Si profundizamos en uno de estos, podemos ver esas cosas donde es realmente fácil mirarlo y decir: "Guau, no sé lo que está haciendo". Como renders, proveedor de imágenes, IO.



Hay una opción donde puede ocultar las bibliotecas del sistema. Es realmente tentador esconderse, pero en realidad, este es en realidad el mayor cuello de botella en la aplicación.

Existen los pesos que muestran qué porcentaje del trabajo está haciendo esta función o método en particular. Y si profundizamos en el ejemplo, tenemos un 34% y sucede debido a Apple jpeg_decode_image_all. Después de un poco de investigación, queda claro que significa que la decodificación JPEG está ocurriendo en el hilo principal y provoca que la mayoría de los fotogramas caigan.



Regla # 2


En general, es mejor decodificar archivos JPEG en segundo plano. La mayoría de las bibliotecas de terceros (AsyncDisplayKit, SDWebImage, ...) lo hacen de forma inmediata. Si no desea utilizar marcos, puede hacerlo usted mismo. Lo que hace es pasar una imagen, en este caso, es una extensión de UIImage, y luego configura un contexto y dibuja la imagen manualmente en un contexto en un CGBitmap.



Cuando haces eso, puedes llamar al método Decodificado Image () desde un hilo de fondo. Eso siempre devolverá la imagen decodificada. No hay forma de verificar si UIImage en particular ya está decodificado, y siempre tiene que pasarlos por aquí. Pero si almacena las cosas correctamente, no hace ningún trabajo adicional.

Hacer esto es técnicamente menos eficiente. Usar UIimageView está súper optimizado, súper eficiente. Hará decodificación de hardware, por lo que es una compensación. Sus imágenes serán decodificadas más lentamente de esta manera. Pero lo bueno es que puede enviar a una cola de fondo, decodificar su imagen con el método que acabamos de ver, y luego volver al hilo principal y configurar su contenido.



Aunque ese trabajo tomó más tiempo, tal vez no sucedió en el hilo principal, por lo que no bloqueó la interacción del usuario ya que no bloqueó el desplazamiento. Entonces eso es una victoria.

Advertencias de memoria


Cualquier signo de que reciba una advertencia de memoria que desea eliminar todo, elimine toda la memoria no utilizada que pueda. Pero si tiene cosas que suceden en subprocesos en segundo plano, la asignación de estos grandes archivos JPEG decodificados ocupa mucha memoria nueva en subprocesos en segundo plano.

Esto sucedió en la aplicación Fyuse. Si saltara a un hilo de fondo, decodificara todos mis archivos JPEG, en algunos casos en teléfonos similares, el sistema lo mataría instantáneamente. Y eso se debe a que está enviando una advertencia de memoria que dice: “¡Hola! Deshágase de su memoria "pero las colas de fondo no escuchan. Qué sucede si está asignando todas estas imágenes y luego se bloquea cada vez. La solución a esto es hacer ping al hilo principal desde el hilo de fondo.



En general, el hilo principal es una cola. Las cosas se ponen en cola y suceden en el hilo principal. Cuando va al fondo en Objective-C, puede usar performSelectorOnMainThread: withObject: waitUntilDone:. Esto lo colocará al final de la línea de colas principales, por lo que si la cola principal está ocupada procesando advertencias de memoria, esta llamada de función irá al final de la línea y esperará a que se procesen todas las advertencias de memoria antes de realizar toda esta pesada asignación de memoria

En Swift, es más simple. Puede hacer un envío del bloque vacío principal sincrónicamente en main.

Aquí hay un ejemplo en el que hemos limpiado cosas y estamos haciendo decodificación de imágenes en colas de fondo. Y el desplazamiento visual es mucho más bonito. Todavía tenemos caídas de fotogramas, pero esto está en un iPod 5g, por lo que es una de las peores cosas en las que puedes probar y que todavía es compatible con iOS 10 y 11.



Cuando tenga estas caídas de cuadros, puede seguir buscando. Todavía hay trabajo que está sucediendo y está causando la caída de estos cuadros. Hay más cosas que puedes hacer para hacerlo más rápido.

En resumen, no siempre es tan fácil, pero si tiene pequeñas cosas que toman mucho tiempo, puede hacerlas en segundo plano.

Asegúrese de que no esté relacionado con UIKit. Muchas clases de UIKit no son seguras para subprocesos y no puede asignar esa UIView en segundo plano.

Use Core Graphics si necesita hacer imágenes en segundo plano. No oculte las bibliotecas del sistema. Y no te olvides de las advertencias de memoria.

Esta es la primera parte de un artículo basado en la presentación de Luke Parham. Si desea obtener más información sobre la forma en que funciona la interfaz de usuario en iOS, por qué utilizar una ruta más clara y cuándo recurrir a la gestión de memoria manual, lea la segunda parte de un artículo aquí .

Video


Mira la charla completa aquí:

Source: https://habr.com/ru/post/es436360/


All Articles