← Todos los articulos

Las novedades de Instruments 27 para la fluidez de las apps

En la WWDC 2026, dos ingenieros de Apple perfilaron una app de notas que mostraba tres bloqueos distintos: un lápiz que dejaba de responder al guardar, un desplazamiento entrecortado y un atasco al usar la herramienta de lazo. Corrigieron los tres asociando una nueva herramienta de Instruments 27 a cada síntoma y luego demostraron cada arreglo con una comparación entre la versión de referencia y la optimizada.1 La tesis de la sesión es un flujo de diagnóstico: lee la CPU durante el bloqueo y la CPU te dirá qué instrumento abrir a continuación. Una CPU alta significa que tu código es demasiado lento. Una CPU inactiva significa que tu código está bloqueado. Instruments 27 trae las herramientas para resolver ambos casos y una nueva vista para confirmar que el arreglo realmente funcionó.

Este artículo recorre las tres herramientas que sostienen ese flujo: Top Functions para encontrar el peso propio más alto cuando la CPU está saturada, el nuevo instrumento Swift executors para ver en qué executor se ejecutó una tarea cuando el trabajo compite por el Main Actor, y el nuevo panel Inspector para leer los argumentos de una llamada al sistema cuando un hilo queda inactivo esperando al sistema. Run Comparisons lo une todo al medir si cada cambio mejoró la traza. Todo lo que sigue proviene directamente de la sesión.

TL;DR

  • Instruments 27 reorganiza el flujo de trabajo de fluidez en torno a una regla de diagnóstico: abre primero el Time Profiler, revisa la CPU del hilo principal durante el bloqueo y deja que esa lectura te lleve a la herramienta correcta.1
  • Top Functions es un nuevo modo de análisis que descarta la jerarquía de llamadas y fusiona cada nodo disperso según el peso propio, sacando a la luz la sobrecarga de ejecución que un flame graph fragmenta entre las ramas.1
  • Run Comparisons es «New in Instruments» y calcula la diferencia exacta de rendimiento entre una traza de referencia y una traza optimizada, emparejando cada función entre ejecuciones y coloreando las regresiones en rojo y las mejoras en verde.1
  • El nuevo instrumento Swift executors visualiza el Main Actor, el executor concurrente global y cualquier executor personalizado, de modo que puedes ver en qué executor se ejecutó una tarea y detectar contención del Main Actor.1
  • El nuevo panel Inspector revela los argumentos exactos de una llamada al sistema (descriptor de archivo, dirección del búfer, tamaño de escritura) y separa el tiempo on-core del off-core, exponiendo un bloqueo síncrono como una escritura de 1,7 GB en el hilo principal.1

El flujo de diagnóstico en torno al que se construye Instruments 27

Antes de cualquier herramienta nueva, la sesión establece la regla que las organiza. Cuando una app pierde fotogramas o se bloquea, el primer paso es el Time Profiler, que ofrece la visión de alto nivel para orientarte.1 A partir de ahí, una sola pregunta lo encamina todo: ¿qué está haciendo la CPU durante el bloqueo?

Si el uso de la CPU es alto, el hilo está ocupado y el trabajo tarda demasiado, lo que apunta a un cuello de botella de rendimiento en el código. Eso se corrige de dos maneras: refactorizando el algoritmo para que se ejecute más rápido o, cuando la carga pesada es inevitable, descargándola a una tarea en segundo plano para que la interfaz siga respondiendo.1 Si la app se bloquea mientras el procesador está inactivo, optimizar algoritmos no ayudará, porque el hilo principal está atascado esperando a que se libere un recurso: E/S de archivo, un bloqueo de sincronización o comunicación entre procesos. Como dice la sesión: «Como el Time Profiler solo monitorea los ciclos activos de CPU, no ofrece ninguna visibilidad sobre estos eventos.»1

Los ingenieros perfilaron una compilación release de la app de notas, porque «una compilación debug sacrifica el rendimiento en tiempo de ejecución a cambio de facilidad de depuración, así que los datos de perfilado de las compilaciones debug pueden resultar engañosos.»1 Eligieron la plantilla Swift Concurrency, que de todos modos expone el instrumento Time Profiler, y registraron los tres bloqueos en una sola traza de referencia. También envolvieron la selección con lazo en un intervalo os_signpost mediante el tipo OSSignposter, fijando la categoría en points of interest para que Instruments muestre el intervalo en la pista de points of interest. Ese signpost se convierte en el punto de anclaje para filtrar la traza y, más adelante, para una comparación de ejecuciones sin ruido.1

Watch on Apple Developer ↗

Art y Harjas exponen el flujo de diagnóstico antes de la demo, a partir del minuto 1:50.

La propia ventana de Instruments 27 enmarca el flujo de trabajo. La línea de tiempo en la parte superior muestra pistas horizontales para tareas, actores y executors. El área de detalle de debajo cambia según la pista seleccionada. A la derecha está «un panel Inspector completamente nuevo» que revela detalles y acciones adicionales según lo que selecciones.1 Tres herramientas llenan ese marco, cada una ligada a uno de los tres bloqueos.

Top Functions: cuando la CPU está saturada

El bloqueo del lazo se lee como CPU alta. Tras filtrar la traza al intervalo os_signpost del lazo, el instrumento hangs confirmó varios bloqueos ahí, y al expandir la pista del proceso hasta el hilo principal se vio la CPU «manteniéndose alrededor del 100 % durante este período de tiempo.»1 Una CPU alta significa que el código se está ejecutando pero tarda demasiado, así que el Time Profiler es la herramienta adecuada.

La sesión explica por qué un flame graph por sí solo no basta aquí. El Time Profiler usa un temporizador de hardware para muestrear la pila de llamadas a una tasa predeterminada de un milisegundo, registrando la pila actual en cada núcleo. Cada función muestreada recibe un peso, y la función al fondo de la pila recibe un peso propio, el tiempo dedicado a ejecutar instrucciones directamente dentro de ella.1 Un flame graph representa ese árbol de llamadas en bloques espaciales, con los llamadores arriba, los llamados creciendo hacia abajo y el ancho de las barras proporcional al tiempo total de CPU.1 El problema: «para el código que se llama desde muchos lugares, como las funciones del runtime de Swift y varias utilidades auxiliares», el flame graph reparte el costo total entre cada rama que las llama. El tiempo de ejecución se fragmenta en trozos pequeños, lo que «dificulta responder qué funciones concretas consumieron en total la mayor cantidad de ciclos.»1

Esa brecha es la que Top Functions llena. Como describe la sesión el nuevo modo: «Este nuevo modo descarta la jerarquía de llamadas. En su lugar, extrae cada nodo disperso y los fusiona para formar un solo bloque», evaluado según la métrica self.1 Recorrer el flame graph del lazo no mostró ningún culpable único, solo «distintas rutas de código que sumadas resultan lo bastante costosas como para causar bloqueos.»1 Al cambiar a Top Functions, ordenado por peso propio, la entrada superior era swift_project_boxed_opaque_existential, la función del runtime que desempaqueta un existencial para que el código pueda operar sobre él.1

El arreglo estaba en el sistema de tipos, no en la línea de tiempo. El ingeniero le pidió al asistente de código de Xcode que reescribiera el código de dibujo para usar tipos concretos y genéricos en lugar de existenciales, ya que los existenciales pueden variar de tamaño y requieren trabajo adicional para acceder a ellos, lo que resultó demasiado costoso para este caso de uso.1 La lección para la herramienta: Top Functions existe para atrapar la sobrecarga de software dispersa que ninguna rama individual de un flame graph deja en evidencia.

Run Comparisons: demostrar que el arreglo funcionó

Confirmar un arreglo solía significar abrir dos trazas en ventanas separadas y comparar a ojo los datos de Top Functions lado a lado. Instruments 27 reemplaza eso con Run Comparisons, descrito en la sesión como «New in Instruments.»1 «Calcula la diferencia exacta de rendimiento cruzando todas las muestras de la traza de referencia y la traza optimizada», evaluando cada nodo de la pila. Empareja la versión antigua de una función de la ejecución de referencia con la nueva versión de la ejecución optimizada, calcula la diferencia y ordena por diferencia de rendimiento. Un bloque rojo marca una regresión; un bloque verde marca una mejora.1

El flujo de trabajo es deliberado a la hora de eliminar el ruido. Los ingenieros primero filtraron ambas ejecuciones exactamente al mismo intervalo os_signpost para la selección con lazo, luego seleccionaron la pista del hilo principal y pulsaron el botón de comparar para elegir la ejecución de referencia en un menú desplegable.1 Eso añade una pestaña de comparación a la barra lateral, y «puedes crear varias comparaciones, y se guardan en el documento para facilitar la colaboración.»1

La comparación contó una historia honesta. El árbol de llamadas textual mostró que el tiempo total de ejecución del lazo disminuyó. El flame graph mostró las rutas mejoradas en verde y las rutas con regresión en rojo, y las regresiones eran «nuevas funciones añadidas por el asistente de código mientras trabajaba para eliminar el uso de existenciales.»1 En la vista Top Functions, las regresiones se ordenan de forma predeterminada en la parte superior; invertir el orden reveló que swift_project_boxed_opaque_existential «se ha eliminado por completo» y que «en general, las mejoras superan (out[weigh]) a las regresiones.»1 Esa diferencia es el paso de verificación: Run Comparisons existe para que un arreglo sea un resultado medido y no uno esperanzado. El consejo permanente de la sesión es «aprovechar os_signpost para asegurarte de que tus intervalos para las comparaciones de ejecuciones sean fiables.»1

El instrumento Swift executors: cuando las tareas compiten por el Main Actor

El bloqueo del desplazamiento no tenía ningún registro de points of interest en el que apoyarse. La sesión recurre a otro tipo de contexto: «qué tareas hay en el Main Actor durante estos bloqueos.»1 Esa es la labor del nuevo instrumento Swift executors, que «visualiza el Main Actor, el executor concurrente global y cualquier executor personalizado de tu proceso.»1

En cada bloqueo del desplazamiento, la pista del Main Actor mostraba una tarea Swift llamada renderThumbnail. Seleccionar la pista resumía las tareas en el Main Actor y revelaba «varias tareas render thumbnail en el Main Actor que tardaban unos cientos de ms en ejecutarse», lo que coincide con el desplazamiento entrecortado.1 Siguiendo el flujo de diagnóstico, el ingeniero filtró a un solo bloqueo y usó el Inspector para fijar el hilo principal; el Time Profiler informó de una CPU «alrededor del 100 %», descartando una espera por recursos del sistema. Las tareas simplemente tardaban demasiado en el Main Actor.1

La causa raíz es una sutileza de herencia de contexto que el instrumento hace visible. El Main Actor gestiona todas las actualizaciones e interacciones de la interfaz. La app renderizaba las miniaturas de forma asíncrona, pero como ese código se llamaba desde SwiftUI, «heredó el contexto del Main Actor», así que las tareas de miniaturas competían con actualizaciones críticas de la interfaz.1 El arreglo encamina el trabajo al pool de hilos. El ingeniero añadió el atributo @concurrent al inicializador de la tarea, lo que mueve la tarea de renderizado fuera del Main Actor y al executor global, y el compilador de Swift verifica que el cambio no introduzca condiciones de carrera.1 El instrumento confirmó el movimiento: en la traza actualizada, «las tareas de renderizado de miniaturas se han movido de la pista del Main Actor a la pista del executor global», y el trabajo ahora se ejecuta en paralelo.1 El punto clave para la herramienta: el instrumento Swift executors existe para identificar la congestión de actores mostrándote exactamente qué executor ejecutó qué tarea.

El panel Inspector: cuando el hilo está inactivo y bloqueado

El bloqueo al guardar invierte los dos primeros. Tras filtrar al intervalo os_signpost Write to File y hacer zoom, el microbloqueo reportado mostraba una CPU «rondando el 20 %.»1 Una CPU baja es engañosa: «no significa que tu código se esté ejecutando despacio, significa que el hilo ha dejado de ejecutarse.»1 Cuando la interfaz se congela con CPU baja, el hilo principal está bloqueado esperando un recurso del sistema, y optimizar algoritmos no aporta nada «porque no hay código ejecutándose que optimizar.»1

Para ese síntoma, la sesión cambia a la plantilla System Trace, «creada para visualizar exactamente cuándo y por qué el sistema operativo pausa tu aplicación.»1 La sesión recorre el modelo de estados del hilo: un hilo en ejecución que se topa con un recurso no disponible entra en estado bloqueado, el kernel lo expulsa del procesador, y solo cuando el recurso está listo pasa a estar ejecutable (runnable) y espera a que el planificador le asigne un núcleo libre. Esos breves despertares para coordinar la siguiente etapa de la petición son exactamente «lo que causa ese veinte por ciento de utilización de CPU.»1

En el System Trace, el carril de actividad del guardado mostraba «una gran cantidad de espacio en blanco», indicando que el hilo estaba bloqueado, con intervalos morados que marcaban una llamada al sistema en ejecución.1 Seleccionar un intervalo resaltaba más que el segmento en el que se hizo clic, visualizando «una sola llamada al sistema de escritura continua que abarca tanto tiempo on-core como off-core»: los segmentos opacos son ejecución on-core, los segmentos translúcidos son bloqueo off-core.1

El nuevo panel Inspector convierte eso en un veredicto. Como afirma la sesión: «El Inspector nos da los argumentos exactos pasados a esta llamada al sistema. Podemos ver el descriptor de archivo de destino, la dirección de memoria del búfer y, lo más importante, el tamaño.»1 El tamaño era la prueba contundente: la app «intentaba escribir más de 1,7 gigabytes de datos en el hilo principal.»1 El Inspector también mostró el costo: la única operación «tardó más de 500 milisegundos, y casi 300 de esos milisegundos se pasaron off-core esperando al disco.»1 La llamada síncrona data.write era el cuello de botella. Envolver la codificación y la escritura en una tarea Swift la empujó al pool de hilos concurrente, y una traza de verificación confirmó que la llamada al sistema de escritura ahora aparece en un hilo en segundo plano, no en el hilo principal.1 El panel Inspector existe para exponer el comportamiento de bloqueo síncrono, como las E/S de archivo, que un perfilador centrado solo en la CPU no puede ver.

Puntos clave

Para ingenieros de iOS y macOS:

  • Abre primero el Time Profiler y lee la CPU del hilo principal durante el bloqueo; una CPU alta te envía a Top Functions, una CPU inactiva te envía a System Trace y al Inspector.1
  • Usa Top Functions cuando un flame graph no muestre ningún culpable único; fusiona la sobrecarga de ejecución dispersa por peso propio para que la función más costosa salga a la luz.1

Para equipos que entregan arreglos de rendimiento:

  • Verifica cada cambio con Run Comparisons, filtrado al mismo intervalo os_signpost, y lee la diferencia rojo/verde en lugar de confiar en una comparación a ojo lado a lado.1
  • Las comparaciones se guardan en el documento y apilan varias ejecuciones, de modo que la evidencia de un arreglo viaja con la traza para su revisión.1

Para el trabajo de concurrencia y fluidez:

  • Recurre al instrumento Swift executors cuando el trabajo compita por el Main Actor; muestra si una tarea se ejecutó en el Main Actor, en el executor concurrente global o en un executor personalizado.1
  • Perfila siempre una compilación release, ya que las compilaciones debug producen datos engañosos.1

Preguntas frecuentes

¿Qué es el modo Top Functions en Instruments 27?

Top Functions es un nuevo modo de análisis del Time Profiler. Descarta la jerarquía de llamadas y fusiona cada instancia dispersa de una función en un solo bloque, ordenado por peso propio, el tiempo dedicado a ejecutar instrucciones directamente dentro de esa función. Responde a una pregunta con la que un flame graph batalla: qué funciones concretas consumieron en total la mayor cantidad de ciclos cuando su costo está fragmentado entre muchas ramas que las llaman, como las funciones del runtime de Swift y las utilidades auxiliares.1

¿Cómo funcionan las Run Comparisons en Instruments?

Run Comparisons, descrito en la sesión como nuevo en Instruments, calcula la diferencia exacta de rendimiento entre una traza de referencia y una traza optimizada. Empareja cada función entre las dos ejecuciones, calcula la diferencia para cada nodo de la pila y ordena por diferencia de rendimiento, coloreando las regresiones en rojo y las mejoras en verde. Para una comparación limpia, filtras ambas ejecuciones al mismo intervalo os_signpost, seleccionas una pista y eliges la referencia en un menú desplegable; las comparaciones se guardan en el documento.1

¿Qué muestra el instrumento Swift executors?

El instrumento Swift executors de Instruments 27 visualiza el Main Actor, el executor concurrente global y cualquier executor personalizado de tu proceso. Te permite ver en qué executor se ejecutó una tarea Swift determinada, de modo que puedas detectar contención del Main Actor. En la sesión reveló tareas renderThumbnail atascadas en el Main Actor porque el código llamado desde SwiftUI heredaba el contexto del Main Actor; moverlas al executor global eliminó el bloqueo.1

¿Cómo encuentras un bloqueo causado por E/S de archivo?

Cuando la interfaz se congela pero la CPU del hilo principal es baja (alrededor del 20 por ciento en la sesión), el hilo está bloqueado esperando un recurso del sistema en lugar de ejecutar código lento. Cambia a la plantilla System Trace y selecciona el intervalo de la llamada al sistema; el nuevo panel Inspector muestra los argumentos exactos del syscall, incluidos el descriptor de archivo, la dirección del búfer y el tamaño de escritura, además del tiempo on-core frente al off-core. En la demo expuso una escritura síncrona de 1,7 GB en el hilo principal.1


El flujo de diagnóstico que enseña esta sesión se complementa con la cara SwiftUI de la fluidez en Rendimiento e interoperabilidad de SwiftUI en iOS 27 y con la mentalidad de medición en el punto ciego del rendimiento. El movimiento de concurrencia que eliminó el bloqueo del Main Actor se sitúa dentro del modelo más amplio que cubre La concurrencia de Swift 6.2 en la práctica. El centro completo de la serie es la Apple Ecosystem Series.

Referencias


  1. Apple, WWDC 2026 session 268, Profile, fix, and verify: Improve app responsiveness with Instruments. Fuente del flujo de diagnóstico (Time Profiler primero, la lectura de la CPU del hilo principal encamina la investigación), de las recomendaciones sobre la compilación release y la plantilla Swift Concurrency, del intervalo points of interest os_signpost / OSSignposter, del nuevo panel Inspector, del modelo de muestreo del árbol de llamadas y del flame graph (tasa predeterminada de un milisegundo, peso propio), del modo de análisis Top Functions y del hallazgo de swift_project_boxed_opaque_existential, de Run Comparisons («New in Instruments») con las diferencias rojo/verde y las pestañas de comparación guardadas en el documento, del instrumento Swift executors (Main Actor, executor concurrente global, executors personalizados) y de la contención del Main Actor de renderThumbnail resuelta con el atributo @concurrent, así como del diagnóstico con System Trace más Inspector de una escritura síncrona de 1,7 GB (descriptor de archivo, dirección del búfer, tamaño de escritura, tiempo on-core frente a off-core, más de 500 ms de los cuales casi 300 ms off-core). 

Artículos relacionados

Lo que dijo el equipo de rendimiento de Apple en el lab de la WWDC26

El equipo de Power & Performance de Apple respondió en vivo a las preguntas de los desarrolladores en la WWDC26. Guía de…

14 min de lectura

MetricKit reconstruido: telemetría consciente del estado en iOS 27

MetricKit se reconstruye en iOS 27: streams asíncronos de métricas y diagnósticos, reportes Codable y un framework State…

13 min de lectura

De 76 a 100: Cómo lograr una puntuación perfecta en Lighthouse

Cómo un sitio de portafolio personal pasó de una puntuación de rendimiento móvil en Lighthouse de 76 con un CLS de 0,493…

7 min de lectura