Rendimiento en iOS o cómo descargar el hilo principal. Parte 1



Hay varios trucos y trucos que ayudan a optimizar el trabajo de las aplicaciones iOS, cuando una tarea debe completarse en 16.67 milisegundos. Te contamos cómo descargar el hilo principal y qué herramientas son más adecuadas para rastrear la pila de llamadas en él.


“Chicos, imaginemos que puede reducir el tiempo de inicio en 10 segundos. Multiplicando esto por 5 millones de usuarios, tendremos 50 millones de segundos diarios. En un año, esto equivaldrá a unas diez vidas humanas. Por lo tanto, si realiza la descarga inicial 10 segundos más rápido, salvará varias decenas de vidas. Realmente vale la pena, ¿no?

Steve Jobs sobre el rendimiento (tiempo de inicio de la computadora Apple II).


El artículo se basa en un informe del desarrollador de Fyusion para iOS Luc Parham, quien habló en la Conferencia Internacional de Desarrolladores Móviles MBLT DEV el año pasado.


MBLT DEV 2018 se llevará a cabo en Moscú el 28 de septiembre. Los boletos son los más baratos en este momento. Por tradición, mientras el Comité del Programa selecciona informes, puede comprar boletos anticipados para konf. Aprovecha esta oportunidad ahora. A partir del 29 de junio, los boletos serán más caros.


Pérdida de trama


El hilo principal ejecuta código que es responsable de los eventos de tipo táctil y de trabajar con la IU. Él renderiza la pantalla. La mayoría de los teléfonos inteligentes modernos rinden a 60 cuadros por segundo. Esto significa que las tareas deben completarse en 16,67 milisegundos (1000 milisegundos / 60 cuadros). Por lo tanto, la aceleración en el hilo principal es importante.


Si alguna operación lleva más de 16.67 milisegundos, la pérdida de cuadros se produce automáticamente y los usuarios de la aplicación lo notarán al reproducir animaciones. En algunos dispositivos, la representación es aún más rápida, por ejemplo, en el iPad Pro 2017, la frecuencia de actualización de la pantalla es de 120 Hz, por lo que solo hay 8 milisegundos para completar las operaciones en un cuadro.


Regla n. ° 1


CADisplayLink es un temporizador especial que se inicia durante la sincronización vertical (Vsync). La sincronización vertical asegura que no se asignen más de 16,67 milisegundos para renderizar una trama. Como comprobación en AppDelegate, puede registrar CADisplayLink en el ciclo de ejecución principal, y luego tendrá una función adicional que realizará los cálculos. Puede realizar un seguimiento de la duración de la aplicación y averiguar cuánto tiempo ha pasado desde el último lanzamiento de esta función.


.


El inicio ocurre cuando aparece la necesidad de renderizar. Si se realizaron muchas operaciones diferentes que sobrecargaron el hilo principal, entonces esta función comienza con un retraso de 100 milisegundos. Esto significa que se hizo demasiado trabajo y en ese momento hubo una pérdida de personal.


Aquí está la aplicación Catstagram. Mientras descarga imágenes, la aplicación comienza a ralentizarse. Vemos que la velocidad de fotogramas disminuyó en cierto punto, y el tiempo de carga duró aproximadamente 200 milisegundos. Algo parece estar tomando demasiado tiempo.


.


Los usuarios no estarán encantados con esto, especialmente si la aplicación se ejecuta en dispositivos más antiguos, como iPhone 5 o modelos de iPod más antiguos, etc.


Perfil de tiempo


Una herramienta útil para rastrear estos problemas es Time Profiler. Otras herramientas también son útiles, pero en última instancia en Fyusion el 90% del tiempo usamos Time Profiler. Por lo general, los problemas en una aplicación están relacionados con ScrollView, áreas con texto e imágenes.


Las imágenes son importantes Decodificamos el formato JPEG usando UIImage . Lo hacen lentamente, y no podemos seguir su desempeño directamente. Esto no sucede inmediatamente después de configurar la imagen en UIImageView , pero puede ver este momento a través del rastreo en Time Profiler.


El formato del texto es otro punto importante. Importa cuando la aplicación tiene una gran cantidad de texto "complejo", por ejemplo, en japonés o chino. Puede tomar mucho tiempo calcular los tamaños correctos para las líneas con texto.


El marcado de la interfaz también ralentiza el renderizado en la aplicación. Esto es especialmente cierto para la herramienta AutoLayout. AutoLayout es conveniente de usar, pero ralentiza enormemente la aplicación en comparación con el marcado manual. Tenemos que hacer concesiones. Si AutoLayout ralentiza la aplicación, puede ser hora de abandonarla y probar otros tipos de marcado.


Patrón de traza




En este árbol de llamadas de ejemplo, puede ver qué tipo de trabajo realiza la CPU. Puede cambiar el tipo de rastreo, verlo desde el punto de vista de hilos, procesadores. Por lo general, lo más interesante es dividir la traza en hilos y monitorear el hilo principal.


El análisis de traza inicial puede parecer complicado. No siempre es posible descubrir de inmediato qué significa FRunLoopDoSource0 .


Al hurgar en la traza, puede comprender cómo funciona el sistema, y ​​luego todo tiene sentido. Puede seguir el rastro de la pila y ver todos los elementos del sistema que no escribió. Pero en la parte inferior está tu código fuente.


Árbol de llamadas


Supongamos que tenemos una aplicación muy simple. Contiene la función principal que llama a varias otras funciones. La esencia del trabajo de Time Profiler es que toma instantáneas del estado actual de la traza de la pila con una frecuencia de un milisegundo (por defecto). Después de otro milisegundo, toma una instantánea del rastro. Llama a la función principal, que llama a la función " foo ", que llama a la función " bar ". El seguimiento inicial de la pila se muestra en la captura de pantalla a continuación. Estos datos se recopilan juntos. Frente a cada función, se indica un número: 1, 1, 1.




Esto significa que cada una de estas funciones se llamó una vez. Luego, después de un milisegundo, tenemos otra toma de la pila. Esta vez se ve exactamente igual, por lo que todos los números aumentan en 1, y obtenemos 2, 2, 2.




Durante el tercer milisegundo, nuestra pila de llamadas se ve un poco diferente. La función principal llama a la bar directamente. Por lo tanto, se agrega una unidad más a la función principal y a la función " bar ", y su valor se convierte en 3. Luego, se produce la separación. A veces, la función principal llama " foo " directamente, a veces " bar " se llama directamente. Esto sucedió una vez. Una función fue llamada a través de otra.


Luego, una función llamó a otra, que llamó a la tercera función. Vemos que la función " baz " se llamó dos veces. Pero esta función es tan insignificante que se llama más rápido que un milisegundo.


Al usar Time Profiler, es importante recordar que no muestra intervalos de tiempo específicos. No muestra el tiempo exacto de ejecución de una función. Solo informa con qué frecuencia aparece en las imágenes, lo que proporciona solo un valor aproximado de la duración de cada función. Dado que algunos procesos son lo suficientemente rápidos, nunca se muestran en las imágenes.




Al cambiar las llamadas al modo de consola, puede ver y comparar todos los momentos de una disminución en la velocidad de fotogramas. En el ejemplo, la pérdida de trama ocurrió varias veces y se realizaron varios procesos.




Al hacer clic en Alt-clic en macOS se expandirá la sección y subsecciones, no solo la seleccionada. Se ordenarán por la cantidad de trabajo realizado. En el 90% de los casos, CFRunLoopRun es lo CFRunLoopRun , seguido de las devoluciones de llamada.


Esta aplicación se basa completamente en un solo ciclo de ejecución de tarea Run Loop. Hay un ciclo que se repite sin cesar, y en cada iteración, se lanzan devoluciones de llamada. Si observa estas devoluciones de llamada, puede resaltar los principales cuellos de botella.


Habiendo analizado estos desafíos con más detalle, lo más probable es que no entiendas lo que están haciendo. Estos pueden ser renders, proveedor de imágenes, IO.




Hay una opción que le permite ocultar las bibliotecas del sistema. En realidad, son las áreas problemáticas de la aplicación.


Hay medidores que, en términos porcentuales, muestran cuánto trabajo realiza una función u operación en particular. Si miramos este ejemplo, veremos aquí el valor: 34%. Este es el proceso Apple jpeg_decode_image_all . Después de estudiar, queda claro que la decodificación de imágenes JPEG se produce en el hilo principal, y en la mayoría de los casos esta es la causa de la pérdida de cuadros.




Regla # 2


En general, la decodificación de imágenes JPEG se debe hacer en segundo plano. La mayoría de las bibliotecas de terceros (AsyncDisplayKit, SDWebImage, etc.) pueden hacer esto de manera predeterminada. Si no desea utilizar marcos, puede hacer la decodificación manualmente. Para hacer esto, puede escribir una extensión sobre UIImage en la que cree un contexto y dibuje manualmente una imagen.




Al realizar esta operación, puede llamar a la función decodeImage no desde el hilo principal. Siempre devolverá una imagen decodificada. No hay forma de verificar si una imagen UIImage en particular ha pasado la decodificación, por lo que siempre debe pasarlos a través de este método. Pero si almacena los datos en caché correctamente, no habrá procesos innecesarios en el sistema.


Desde un punto de vista técnico, esto es menos efectivo. Usar la clase UIImageView parece optimizado y eficiente. Pero también realiza la decodificación de hardware, por lo que también tiene sus inconvenientes. Con este método, sus imágenes se decodificarán más lentamente. Pero hay buenas noticias: puede decodificar la imagen de la manera anterior, no en el hilo principal, y luego volver al hilo principal y configurar la interfaz.




A pesar de que esta operación requiere más tiempo, es posible que no se realice en el hilo principal, lo que significa que no interfiere con la actividad del usuario en la aplicación, ya que no ralentiza el desplazamiento de la cinta. Solución rentable


Alertas sin memoria


Con cualquier señal de poca memoria, quiero eliminar todos los datos no utilizados que sea posible. Pero si se realizan varios procesos en transmisiones de terceros, entonces la colocación de imágenes JPEG decodificadas volumétricas en ellas ocupará la mayor parte del espacio libre.


Tal problema ocurrió en la aplicación Fyuse. Si hubiera decodificado todas mis imágenes JPEG en una transmisión de terceros, entonces, en algunos casos, por ejemplo, en modelos de teléfonos más antiguos, este sistema interrumpiría instantáneamente la aplicación. Esto se debe al hecho de que los flujos de tareas de terceros no responden a una advertencia sobre memoria insuficiente del sistema, como "¡Hey, elimine datos innecesarios!". Se produce la siguiente situación: primero coloca todas estas imágenes en transmisiones de terceros y luego la aplicación se bloquea constantemente. Si los subprocesos de terceros envían señales al subproceso principal sobre lo que está sucediendo en el sistema, entonces ese problema no ocurrirá.


Trabaja sin fallas




Esencialmente, el hilo principal es una cola que consta de procesos. Cuando trabaje con subprocesos de terceros, puede escribir el comando performSelectorOnMainThread:withObject:waitUntilDone: en Objective-C. Gracias a ella, las tareas se colocarán al final de la cola en el hilo principal. Por lo tanto, si el hilo principal está ocupado procesando notificaciones de memoria insuficiente, llamar a este comando le permitirá esperar hasta que se hayan procesado todas las notificaciones, y solo entonces comenzará el complejo proceso de cargar y colocar datos. En Swift, esto parece un poco más simple. DispatchQueue.main.sync libera espacio en el hilo principal.


Aquí hay otro ejemplo. Hemos liberado memoria y decodificado imágenes en transmisiones de terceros. El desplazamiento visual de la cinta se ha vuelto mucho mejor. Todavía estamos perdiendo fotogramas debido al hecho de que estamos probando el iPod 5g. Este es uno de los peores modelos de prueba de aquellos que aún admiten las versiones 10 y 11 de iOS.




Si experimenta este tipo de pérdida de cuadros, aún puede ver la cinta. Sin embargo, quedan procesos que continúan creando pérdidas de personal. Hay otras formas de hacer que la aplicación se ejecute más rápido.


Por supuesto, no siempre es fácil optimizar la aplicación. Pero si tiene tareas que tardan relativamente tiempo en completarse, debe colocarlas en los hilos de fondo. Asegúrese de que estas tareas no estén relacionadas con la interfaz de usuario, ya que muchas clases de UIKit no son seguras para subprocesos, es decir, no puede crearlas en el back-end.


Use Core Graphics si necesita procesar imágenes en una transmisión de terceros. No oculte la visualización de las bibliotecas del sistema. Recuerde las advertencias sin memoria.


Bienvenido a MBLT DEV 2018


Ven el 28 de septiembre a la 5ta Conferencia Internacional de Desarrolladores Móviles MBLT DEV 2018 en Moscú. Los primeros oradores ya están en el sitio, y el último pájaro temprano todavía está a la venta. Los precios de las entradas subirán el 29 de junio. Compre boletos ahora al precio más bajo.



Lea sobre la implementación de la interfaz de usuario en iOS, el uso de las curvas de Bezier y otras herramientas útiles en la segunda parte del artículo, que publicaremos el 28 de junio.

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


All Articles