← Todos os Posts

O que há de novo no Instruments 27 para a responsividade de apps

Na WWDC 2026, dois engenheiros da Apple analisaram um app de anotações que apresentava três travamentos distintos: o lápis não respondia ao salvar, a rolagem ficava engasgada e havia uma parada ao usar a ferramenta de laço. Eles corrigiram os três associando uma nova ferramenta do Instruments 27 a cada sintoma e, depois, provaram cada correção com uma comparação entre a baseline e a versão otimizada.1 A tese da sessão é um fluxo de diagnóstico: leia a CPU durante o travamento, e a CPU dirá qual instrument abrir em seguida. CPU alta significa que seu código está lento demais. CPU ociosa significa que seu código está bloqueado. O Instruments 27 entrega as ferramentas para resolver os dois casos e uma nova visualização para confirmar que a correção realmente funcionou.

Este post percorre as três ferramentas que sustentam esse fluxo: o Top Functions, para encontrar o peso próprio mais alto quando a CPU está saturada; o novo instrument Swift executors, para ver em qual executor uma task rodou quando o trabalho disputa o Main Actor; e o novo painel Inspector, para ler os argumentos de uma syscall quando uma thread fica ociosa esperando pelo sistema. O Run Comparisons amarra tudo medindo se cada mudança melhorou o trace. Tudo o que segue vem diretamente da sessão.

TL;DR

  • O Instruments 27 reorganiza o fluxo de responsividade em torno de uma regra de diagnóstico: abra o Time Profiler primeiro, verifique a CPU da thread principal durante o travamento e deixe essa leitura te direcionar para a ferramenta certa.1
  • O Top Functions é um novo modo de análise que descarta a hierarquia de chamadas e mescla cada nó disperso pelo peso próprio, revelando o overhead de runtime que um flame graph fragmenta entre os ramos.1
  • O Run Comparisons é “New in Instruments” e calcula o delta de desempenho exato entre um trace de baseline e um trace otimizado, associando cada função entre as execuções e colorindo regressões de vermelho e melhorias de verde.1
  • O novo instrument Swift executors visualiza o Main Actor, o global concurrent executor e quaisquer executors personalizados, para que você veja em qual executor uma task rodou e detecte a disputa pelo Main Actor.1
  • O novo painel Inspector revela os argumentos exatos de uma chamada de sistema (file descriptor, endereço do buffer, tamanho da escrita) e separa o tempo on-core do off-core, expondo bloqueios síncronos como uma escrita de 1,7 GB na thread principal.1

O fluxo de diagnóstico em torno do qual o Instruments 27 foi construído

Antes de qualquer ferramenta nova, a sessão estabelece a regra que as organiza. Quando um app perde frames ou trava, o primeiro passo é o Time Profiler, que oferece a visão geral de alto nível para você se orientar.1 A partir daí, uma única pergunta direciona tudo: o que a CPU está fazendo durante o travamento?

Se o uso de CPU está alto, a thread está ocupada e o trabalho está demorando demais, o que aponta para um gargalo de desempenho no código. Você corrige isso de duas formas: refatore o algoritmo para rodar mais rápido ou, quando a carga pesada for inevitável, transfira-a para uma task em segundo plano para que a interface continue responsiva.1 Se o app trava enquanto o processador fica ocioso, otimizar algoritmos não vai ajudar, porque a thread principal está presa esperando algum recurso ser liberado: I/O de arquivo, um lock de sincronização ou comunicação entre processos. Como diz a sessão: “Because Time Profiler only monitors active CPU cycles, it provides no visibility into these events.”1

Os engenheiros analisaram um release build do app de anotações, porque “a debug build trades off runtime performance for debug ability, so profiling data from debug builds can be misleading.”1 Eles escolheram o template Swift Concurrency, que ainda expõe o instrument Time Profiler, e gravaram os três travamentos em um único trace de baseline. Eles também envolveram a seleção de laço em um intervalo de os_signpost usando o tipo OSSignposter, definindo a categoria como points of interest para que o Instruments mostre o intervalo na trilha points of interest. Esse signpost vira a âncora para filtrar o trace e, mais adiante, para uma comparação de execução sem ruído.1

Watch on Apple Developer ↗

Art e Harjas apresentam o fluxo de diagnóstico antes da demonstração, a partir de 1:50.

A própria janela do Instruments 27 emoldura o fluxo de trabalho. A linha do tempo no topo mostra trilhas horizontais para tasks, actors e executors. A área de detalhes abaixo muda conforme a trilha selecionada. À direita fica “a brand new Inspector panel”, que revela detalhes e ações adicionais com base no que você seleciona.1 Três ferramentas preenchem essa moldura, cada uma ligada a um dos três travamentos.

Top Functions: quando a CPU está saturada

O travamento do laço se lê como CPU alta. Depois de filtrar o trace para o intervalo de os_signpost do laço, o instrument hangs confirmou vários travamentos ali, e expandir a trilha do processo até a thread principal mostrou a CPU “staying around a 100% during this time period.”1 CPU alta significa que o código está executando, mas demora demais, então o Time Profiler é a ferramenta certa.

A sessão explica por que um flame graph sozinho não basta aqui. O Time Profiler usa um timer de hardware para amostrar a pilha de chamadas a uma taxa padrão de um milissegundo, registrando a pilha atual em cada núcleo. Cada função amostrada recebe um peso, e a função no fundo da pilha recebe um peso próprio, o tempo gasto executando instruções diretamente dentro dela.1 Um flame graph renderiza essa árvore de chamadas em blocos espaciais, com os chamadores no topo, os chamados crescendo para baixo e a largura da barra proporcional ao tempo total de CPU.1 O problema: “for code that is called from a lot of places, like Swift runtime functions and various helper utilities”, o flame graph distribui o custo total por cada ramo que as chama. O tempo de execução se fragmenta em pedacinhos, o que “makes it difficult to answer which specific functions burned the most overall cycles.”1

É essa lacuna que o Top Functions preenche. Como a sessão descreve o novo modo, “This new mode discards the call hierarchy. Instead, it extracts every single scattered node and merges them together to form one block”, avaliado pela métrica self.1 Examinar o flame graph do laço não revelou um único culpado, apenas “different codepaths sum together to be costly enough to cause hangs.”1 Ao mudar para o Top Functions, ordenado por peso próprio, a entrada no topo era swift_project_boxed_opaque_existential, a função de runtime que desempacota um existential para que o código possa operar sobre ele.1

A correção morava no sistema de tipos, não na linha do tempo. O engenheiro pediu ao assistente de programação do Xcode que reescrevesse o código de desenho para usar tipos concretos e generics em vez de existentials, já que existentials podem variar de tamanho e exigem trabalho extra para serem acessados, o que se mostrou caro demais para esse caso de uso.1 A lição sobre a ferramenta: o Top Functions existe para flagrar o overhead de software disperso que nenhum ramo isolado de um flame graph deixa óbvio.

Run Comparisons: provando que a correção funcionou

Confirmar a correção costumava significar abrir dois traces em janelas separadas e comparar a olho os dados do Top Functions lado a lado. O Instruments 27 substitui isso pelo Run Comparisons, descrito na sessão como “New in Instruments.”1 Ele “computes the exact performance delta by cross-referencing all of samples from the baseline trace and optimized trace”, avaliando cada nó da pilha. Ele associa a versão antiga de uma função da execução de baseline à versão nova na execução otimizada, calcula o delta e ordena pela diferença de desempenho. Um bloco vermelho marca uma regressão; um bloco verde marca uma melhoria.1

O fluxo de trabalho é deliberado em eliminar o ruído. Os engenheiros primeiro filtraram ambas as execuções para o mesmo intervalo exato de os_signpost da seleção de laço, depois selecionaram a trilha da thread principal e clicaram no botão de comparar para escolher a execução de baseline em um menu suspenso.1 Isso adiciona uma aba de comparação à barra lateral, e “you can create multiple comparisons, and they are saved to the document to make collaboration easier.”1

A comparação contou uma história honesta. A árvore de chamadas em texto mostrou que o tempo total de execução do laço diminuiu. O flame graph mostrou os caminhos melhorados em verde e os que regrediram em vermelho, e as regressões eram “new functions added by the coding assistant as it worked to eliminate the usage of existentials.”1 Na visualização Top Functions, as regressões ficam no topo por padrão; ao inverter a ordenação, ficou claro que swift_project_boxed_opaque_existential “has been removed completely”, e “overall, the improvements out[weigh] the regressions.”1 Esse delta é a etapa de verificação: o Run Comparisons existe para que uma correção seja um resultado medido, e não algo só esperançoso. O conselho permanente da sessão é “leverage os_signpost to make sure your intervals for run comparisons are reliable.”1

O instrument Swift executors: quando tasks disputam o Main Actor

O travamento da rolagem não tinha um log de points of interest no qual se apoiar. A sessão recorre a outro tipo de contexto: “what tasks are on the Main Actor during these hangs.”1 Esse é o trabalho do novo instrument Swift executors, que “visualizes the Main Actor, the global concurrent executor, and any custom executors in your process.”1

Em cada travamento de rolagem, a trilha do Main Actor mostrava uma task de Swift chamada renderThumbnail. Selecionar a trilha resumiu as tasks no Main Actor e revelou “several render thumbnail tasks on the Main Actor taking a few hundred ms to run”, o que bate com a rolagem engasgada.1 Seguindo o fluxo de diagnóstico, o engenheiro filtrou para um travamento e usou o Inspector para fixar a thread principal; o Time Profiler reportou a CPU “around a 100%”, descartando uma espera por recurso do sistema. As tasks estavam simplesmente demorando demais no Main Actor.1

A causa raiz é uma sutileza de herança de contexto que o instrument torna visível. O Main Actor cuida de todas as atualizações e interações de UI. O app renderizava as miniaturas de forma assíncrona, mas, como esse código era chamado a partir do SwiftUI, “it inherited the Main Actor context”, então as tasks de miniatura competiam com atualizações críticas de UI.1 A correção encaminha o trabalho para o pool de threads. O engenheiro adicionou o atributo @concurrent ao inicializador da task, o que move a task de renderização para fora do Main Actor e para o global executor, e o compilador Swift verifica que a mudança não introduz condições de corrida.1 O instrument confirmou a mudança: no trace atualizado, “the thumbnail rendering tasks have moved from the Main Actor track to the global executor track”, e o trabalho agora roda em paralelo.1 O ponto sobre a ferramenta: o instrument Swift executors existe para identificar o congestionamento de actors mostrando exatamente qual executor rodou qual task.

O painel Inspector: quando a thread está ociosa e bloqueada

O travamento ao salvar inverte os dois primeiros. Depois de filtrar para o intervalo de os_signpost Write to File e dar zoom, o micro travamento reportado mostrou a CPU “hovering around 20%.”1 CPU baixa é enganosa: “it doesn’t mean your code is executing slowly, it means the thread has stopped running.”1 Quando a interface congela com CPU baixa, a thread principal está bloqueada esperando um recurso do sistema, e otimizar algoritmos não rende nada “because there is no code running to optimize.”1

Para esse sintoma, a sessão muda para o template System Trace, “built to visualize exactly when and why the operating system pauses your application.”1 A sessão percorre o modelo de estados da thread: uma thread em execução que esbarra em um recurso indisponível entra no estado bloqueado, o kernel a remove do processador, e só quando o recurso está pronto é que ela se torna executável e espera o escalonador atribuir um núcleo livre. Esses breves despertares para coordenar a próxima etapa da requisição são exatamente “what cause that twenty percent of CPU utilization.”1

No System Trace, a faixa de atividade do salvamento mostrou “a large amount of blank space”, indicando que a thread estava bloqueada, com intervalos roxos marcando uma chamada de sistema em execução.1 Selecionar um intervalo destacou mais do que o segmento clicado, visualizando “one continuous write system call that spans both on and off-core time”: os segmentos opacos são execução on-core, os segmentos translúcidos são bloqueio off-core.1

O novo painel Inspector transforma isso em um veredito. Como afirma a sessão, “The Inspector gives us the exact arguments passed to this system call. We can see the target file descriptor, the memory address of the buffer, and most importantly, the size.”1 O tamanho era a prova cabal: o app estava “trying to write over 1.7 gigabytes of data on the main thread.”1 O Inspector também mostrou o custo: a única operação “took over 500 milliseconds, and almost 300 of those milliseconds were spent off-core waiting for the disk.”1 A chamada síncrona data.write era o gargalo. Envolver a codificação e a escrita em uma task de Swift a empurrou para o pool de threads concorrentes, e um trace de verificação confirmou que a chamada de sistema de escrita agora aparece em uma thread em segundo plano, não na thread principal.1 O painel Inspector existe para expor o comportamento de bloqueio síncrono, como I/O de arquivo, que um profiler somente de CPU não consegue enxergar.

Principais conclusões

Para engenheiros de iOS e macOS:

  • Abra o Time Profiler primeiro e leia a CPU da thread principal durante o travamento; CPU alta te leva ao Top Functions, CPU ociosa te leva ao System Trace e ao Inspector.1
  • Use o Top Functions quando um flame graph não mostra um único culpado; ele mescla o overhead de runtime disperso por peso próprio para que a função mais cara venha à tona.1

Para times que entregam correções de desempenho:

  • Verifique cada mudança com o Run Comparisons, filtrado para o mesmo intervalo de os_signpost, e leia o delta vermelho/verde em vez de confiar numa comparação visual lado a lado.1
  • As comparações são salvas no documento e empilham várias execuções, então a evidência de uma correção viaja junto com o trace para revisão.1

Para trabalho de concorrência e responsividade:

  • Recorra ao instrument Swift executors quando o trabalho disputa o Main Actor; ele mostra se uma task rodou no Main Actor, no global concurrent executor ou em um executor personalizado.1
  • Sempre faça profiling de um release build, já que debug builds produzem dados enganosos.1

Perguntas frequentes

O que é o modo Top Functions no Instruments 27?

O Top Functions é um novo modo de análise no Time Profiler. Ele descarta a hierarquia de chamadas e mescla cada instância dispersa de uma função em um único bloco, classificado por peso próprio, o tempo gasto executando instruções diretamente dentro daquela função. Ele responde a uma pergunta com a qual um flame graph tem dificuldade: quais funções específicas queimaram mais ciclos no total quando seu custo está fragmentado por muitos ramos de chamada, como funções de runtime do Swift e utilitários auxiliares.1

Como funcionam os Run Comparisons no Instruments?

O Run Comparisons, descrito na sessão como novidade no Instruments, calcula o delta de desempenho exato entre um trace de baseline e um trace otimizado. Ele associa cada função entre as duas execuções, calcula o delta para cada nó da pilha e ordena pela diferença de desempenho, colorindo regressões de vermelho e melhorias de verde. Para uma comparação limpa, você filtra ambas as execuções para o mesmo intervalo de os_signpost, seleciona uma trilha e escolhe a baseline em um menu suspenso; as comparações são salvas no documento.1

O que o instrument Swift executors mostra?

O instrument Swift executors no Instruments 27 visualiza o Main Actor, o global concurrent executor e quaisquer executors personalizados no seu processo. Ele permite ver em qual executor uma dada task de Swift rodou, para que você flagre a disputa pelo Main Actor. Na sessão, ele revelou tasks renderThumbnail presas no Main Actor porque o código chamado pelo SwiftUI herdava o contexto do Main Actor; movê-las para o global executor eliminou o travamento.1

Como encontrar um travamento causado por I/O de arquivo?

Quando a interface congela mas a CPU da thread principal está baixa (cerca de 20% na sessão), a thread está bloqueada esperando um recurso do sistema, e não rodando código lento. Mude para o template System Trace e selecione o intervalo da chamada de sistema; o novo painel Inspector mostra os argumentos exatos da syscall, incluindo o file descriptor, o endereço do buffer e o tamanho da escrita, além do tempo on-core versus off-core. Na demonstração, ele expôs uma escrita síncrona de 1,7 GB na thread principal.1


O fluxo de diagnóstico que esta sessão ensina se combina com o lado SwiftUI da responsividade em Desempenho e interoperabilidade do SwiftUI no iOS 27 e com a mentalidade de medição em o ponto cego do desempenho. A mudança de concorrência que eliminou o travamento do Main Actor faz parte do modelo mais amplo coberto em concorrência do Swift 6.2 na prática. O hub completo da série é a Série Apple Ecosystem.

Referências


  1. Apple, sessão 268 da WWDC 2026, Profile, fix, and verify: Improve app responsiveness with Instruments. Fonte para o fluxo de diagnóstico (Time Profiler primeiro, a leitura de CPU da thread principal direciona a investigação), a orientação sobre release build e o template Swift Concurrency, o intervalo de points of interest os_signpost / OSSignposter, o novo painel Inspector, o modelo de amostragem da call tree e do flame graph (taxa padrão de um milissegundo, peso próprio), o modo de análise Top Functions e a descoberta de swift_project_boxed_opaque_existential, o Run Comparisons (“New in Instruments”) com deltas em vermelho/verde e abas de comparação salvas no documento, o instrument Swift executors (Main Actor, global concurrent executor, executors personalizados) e a disputa pelo Main Actor em renderThumbnail resolvida com o atributo @concurrent, e o diagnóstico via System Trace mais Inspector de uma escrita síncrona de 1,7 GB (file descriptor, endereço do buffer, tamanho da escrita, tempo on-core versus off-core, mais de 500 ms com quase 300 ms off-core). 

Artigos relacionados

O que o time de performance da Apple disse no lab da WWDC26

O time de Power & Performance da Apple respondeu perguntas de desenvolvedores ao vivo na WWDC26. Orientação de campo sob…

14 min de leitura

MetricKit reconstruído: telemetria sensível ao estado no iOS 27

O MetricKit foi reconstruído no iOS 27: streams assíncronos de métricas e diagnósticos, relatórios Codable e um framewor…

13 min de leitura

De 76 a 100: Alcançando uma Pontuação Perfeita no Lighthouse

Como um site pessoal de portfólio saiu de uma pontuação de 76 no Lighthouse mobile com 0,493 de CLS para um perfeito 100…

7 min de leitura