Share via


Analisar o uso de CPU nos aplicativos da Store

Aplica-se ao Windows e Windows Phone

Quando você precisa investigar problemas de desempenho no aplicativo, um bom começo é entender como ele usa a CPU. A ferramenta Uso da CPU no hub de Desempenho e Diagnóstico do Visual Studio mostra a você onde a CPU está gastando tempo, executando código C++, C#/VB e JavaScript. Para se concentrar em cenários específicos, o Uso da CPU pode ser executado com a ferramenta Capacidade de Resposta da Interface de Usuário XAML, a ferramenta Consumo de Energia ou as duas ferramentas em uma única sessão de diagnóstico. A ferramenta Uso da CPU substitui a ferramenta Amostragem de CPU no Visual Studio 2013.

Dica

A ferramenta Uso da CPU não pode ser usada com os aplicativos Windows Phone Silverlight 8.1.

Este passo a passo leva você através da coleta e da análise do uso da CPU por um aplicativo simples.

Você verá os procedimentos padrão do hub de Desempenho e Diagnóstico para coletar os dados, mas esse hub oferece muitas outras opções para executar e gerenciar suas sessões de diagnóstico. Por exemplo, você pode executar a ferramenta Uso da CPU em aplicativos do Windows Phone ou da Windows Store; executar a sessão de diagnóstico no computador com Visual Studio, em um Windows Phone ou em um dispositivo da Windows Store ou em um dos emuladores ou simuladores do Visual Studio.

Então, você analisará atentamente o relatório de diagnóstico de uso da CPU.

Conteúdo

Criar o projeto CpuUseDemo

O que é CpuUseDemo?

Coletar dados de uso da CPU

Analise o relatório de uso da CPU

Próximas etapas

MainPage.xaml

MainPage.xaml.cs

Criar o projeto CpuUseDemo

  1. Crie um novo aplicativo C# da Windows Store chamado CpuUseDemo, usando o modelo BlankApp.

    Crie o CpuUseDemoProject

  2. Substitua o MainPage.xaml por este código.

  3. Substitua o MainPage.xaml.cs por este código.

O que é CpuUseDemo?

CpuUseDemo é um aplicativo que foi criado para demonstrar como coletar e analisar dados de uso da CPU. Os botões geram um número chamando um método que seleciona o valor máximo de várias chamadas a uma função. A função chamada cria um número muito grande de valores aleatórios e retorna o último deles. Os dados são exibidos em uma caixa de texto. Crie o aplicativo e experimente-o.

Interface do usuário CpuUseDemo

O aplicativo não é muito empolgante e os métodos de CpuUseDemo não são muito interessantes, mas é simples o suficiente para mostrar alguns casos comuns da análise de dados de uso da CPU.

Coletar dados de uso da CPU

Executar uma compilação de versão do aplicativo no simulador

  1. No Visual Studio, defina o destino de implantação como Simulador e a configuração da solução como Varejo.

    • Executar o aplicativo no simulador permite que você alterne facilmente entre o aplicativo e o Visual Studio IDE.

    • Executar este aplicativo no modo Liberar dá a você uma melhor visão do desempenho atual do aplicativo.

  2. No menu Depurar, escolha Desempenho e Diagnóstico.

  3. No hub de Desempenho e Diagnóstico, escolha Uso de CPU e Iniciar.

    Iniciar a sessão de diagnóstico CpuUsage

  4. Quando o aplicativo é iniciado, clique em Obter número máximo. Espere cerca de um segundo após a exibição da saída e escolha Obter número máximo assíncrono. Esperar entre cliques de botão torna mais fácil isolar as rotinas de clique do botão no relatório de diagnóstico.

  5. Depois que a segunda linha de saída aparece, escolha Parar de coletar no hub de Desempenho e Diagnóstico.

Parar a coleta de dados CpuUsage

A ferramenta Uso da CPU analisa os dados e exibe o relatório.

Relatório CpuUsage

Analise o relatório de uso da CPU

Gráfico da linha de tempo de utilização da CPU**|Selecionar segmentos da linha de tempo para visualizar os detalhes|Árvore de chamadas de uso da CPU|Estrutura da árvore de chamadas|Código Externo|Colunas de dados da árvore de chamadas|**Funções assíncronas na árvore de chamadas de uso da CPU

Gráfico de linha de tempo do uso da CPU

Gráfico de linha do tempo de utilização da CPU (%)

O gráfico de uso da CPU mostra a atividade da CPU do aplicativo como um percentual de todo o tempo de CPU de todos os núcleos do processador no dispositivo. Os dados deste relatório foram coletados em um computador com dois núcleos. Os dois maiores picos representam a atividade da CPU de dois cliques de botões. GetMaxNumberButton_Click trabalha de modo síncrono em um único núcleo, por isso faz sentido que a altura do gráfico de método nunca exceda 50%GetMaxNumberAsycButton_Click executa de modo assíncrono entre os dois núcleos, por isso, está certo que o pico se aproxime ao uso de todos os recursos da CPU nos dois núcleos.

Selecionar segmentos de linha de tempo para visualizar os detalhes

Use as barras de seleção na linha de tempo Sessão de diagnóstico para se concentrar nos dados GetMaxNumberButton_Click:

GetMaxNumberButton_Click selecionado

Agora, a linha de tempo Sessão de diagnóstico exibe o tempo gasto no segmento selecionado (um pouco mais de 2 segundos, neste relatório) e filtra a árvore de chamadas àqueles métodos que foram executados na seleção.

Agora, escolha o segmento GetMaxNumberAsyncButton_Click.

Seleção de relatório GetMaxNumberAsyncButton_Click

Esse método é concluído cerca de um segundo mais rápido que o GetMaxNumberButton_Click, mas o significado das entradas da árvore de chamadas é menos óbvio.

Árvore de chamadas de uso da CPU

Para começar a compreender as informações da árvore de chamadas, escolha novamente o segmento GetMaxNumberButton_Click, e veja os detalhes da árvore de chamadas.

Estrutura da árvore de chamadas

Árvore de chamadas GetMaxNumberButton_Click

Etapa 1

O nó de nível superior nas árvores de chamada de uso da CPU é um pseudo-nó

Etapa 2

Na maioria dos aplicativos, quando a opção Mostrar Código Externo está desativada, o nó de segundo nível é um nó [Código Externo] que contém o código do sistema e do framework que inicia e para o aplicativo, elabora a interface do usuário, controla o agendamento de segmento e fornece ao aplicativo outros serviços de nível inferior.

Etapa 3

Os filhos do nó de segundo nível são métodos e rotinas assíncronas do código de usuário que são chamados ou criados pelo sistema de segundo nível e código do framework.

Etapa 4

Os nós filhos de um método só contêm dados das chamadas do método pai. Quando Mostrar Código Externo está desativado, os métodos do aplicativo também contêm um nó [Código Externo].

Código externo

O código externo é uma função nos componentes do sistema e do framework executados pelo código que você grava. O código externo inclui funções que iniciam e param o aplicativo, elaboram a interface do usuário, controlam a segmentação e fornecem ao aplicativo outros serviços de nível inferior. Na maioria dos casos, você não se interessará pelo código externo, então, a árvore de chamadas de uso da CPU junta as funções externas de um método de usuário em um nó de [Código Externo].

Quando você quiser visualizar os caminhos da chamada do código externo, escolha Mostrar Código Externo da lista Exibição de filtro e escolha Aplicar.

Escolha o modo de filtro e mostrar código externo

Saiba que muitas correntes de chamada de código externo são muito aninhadas, de forma que a largura da coluna Nome da Função pode exceder a largura da tela de todos os monitores de computador, exceto dos maiores. Quando isso acontece, os nomes das função são mostrados como […]:

Código externo aninhado na árvore de chamadas

Use a caixa de pesquisa para encontrar um nó que você está procurando, em seguida, use a barra de rolagem horizontal para trazer os dados para exibição:

Procurar código externo aninhado

Colunas de dados da árvore de chamadas

CPU total (%)

Equação de dados total %

A porcentagem de atividade da CPU do aplicativo no intervalo de tempo selecionado que foi usado por chamadas para as funções e as funções chamadas pela função. Observe que isso é diferente do gráfico de linha de tempo de Uso da CPU, que compara a atividade total do aplicativo em um intervalo de tempo com a capacidade total disponível da CPU.

CPU própria (%)

Equação % Self

A porcentagem de atividade da CPU do aplicativo no intervalo de tempo selecionado que foi usada pelas chamadas para as funções, exceto a atividade das funções chamadas pela função.

CPU total (ms)

O número de milissegundos gastos em chamadas para a função no intervalo de tempo escolhido e as funções chamadas pela função.

CPU própria (ms)

O número de milissegundos gastos em chamadas para a função no intervalo de tempo escolhido e as funções chamadas pela função.

Módulo

O nome do módulo que contém a função ou o número de módulos que contêm as funções em um nó de [Código Externo].

Funções assíncronas na árvore de chamadas de uso da CPU

Quando o compilador encontra um método assíncrono, ele cria uma classe oculta para controlar o método de execução. Conceitualmente, a classe é uma máquina de estado que inclui uma lista de funções geradas pelo compilador que chamam corretamente as operações do método original de modo assíncrono, os retornos de chamadas, o agendador e os iteradores necessários a eles. Quando o método original é chamado por um método pai, o tempo de execução remove o método do contexto de execução do pai e executa os métodos da classe oculta no contexto do código do sistema e do framework que controla a execução do aplicativo. Os métodos assíncronos são geralmente, mas nem sempre, executados em uma ou mais segmentos diferentes. Esse código é mostrado na árvore de chamadas de uso da CPU como filho do nó [Código Externo] logo abaixo do nó superior da árvore.

Para ver isso em nosso exemplo, escolha novamente o segmento GetMaxNumberAsyncButton_Click na linha de tempo.

Seleção de relatório GetMaxNumberAsyncButton_Click

Os dois primeiros nós no [Código Externo] são os métodos gerados pelo compilador da classe de computador de estado. O terceiro é a chamada ao método original. Expandir os métodos gerados mostra o que está acontecendo.

Árvore de chamada GetMaxNumberAsyncButton_Click expandida

  • MainPage::GetMaxNumberAsyncButton_Click faz muito pouco; gerencia uma lista de valores da tarefa, computa o máximo de resultados e exibe a saída.

  • MainPage+<GetMaxNumberAsyncButton_Click>d__3::MoveNext mostra a atividade necessária para agendar e iniciar as 48 tarefas que encapsulam a chamada para GetNumberAsync.

  • MainPage::<GetNumberAsync>b__b mostra a atividade das tarefas que chamam GetNumber.

Próximas etapas

O aplicativo CpuUseDemo não é o mais perfeito dos aplicativos, mas você pode estender sua utilidade usando-o para experimentos com operação assíncrona e outras ferramentas no hub de Desempenho e Diagnóstico.

  • Observe que MainPage::<GetNumberAsync>b__b gasta mais tempo no [Código Externo] do que para executar o método GetNumber. Muito desse tempo é a sobrecarga de operações assíncronas. Tente aumentar o número de tarefas (definidas na constante NUM_TASKS de MainPage.xaml.cs) e reduzir o número de iterações em GetNumber (mudar o valor de MIN_ITERATIONS). Execute o cenário de coleta e compare a atividade da CPU do MainPage::<GetNumberAsync>b__bcom a da sessão de diagnóstico de uso da CPU original. Tente reduzir as tarefas e aumentar as iterações.

  • Os usuários não se importam com o desempenho real de seu aplicativo; eles se importam com o desempenho percebido e a capacidade de resposta do aplicativo. A ferramenta Capacidade de resposta da interface de usuário XAML mostra os detalhes da atividade no segmento da interface do usuário que afeta a capacidade de resposta percebida.

    Crie uma nova sessão no de hub Desempenho e Diagnóstico e adicione as duas ferramentas Capacidade de resposta da interface de usuário XAML e Uso da CPU. Execute o cenário de coleta. Se você leu até aqui, o relatório provavelmente não diz nada que você ainda não tenha entendido, mas as diferenças no gráfico da linha de tempo de Uso do segmento da interface do usuário dos dois métodos estão tachadas. Em aplicativos complexos do mundo real, a combinação de ferramentas pode ser muito útil.

MainPage.xaml

<Page
    x:Class="CpuUseDemo.MainPage"
    xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:CpuUseDemo"
    xmlns:d="https://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="https://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d">

    <Page.Resources>
        <Style TargetType="TextBox">
            <Setter Property="FontFamily"  Value="Lucida Console" />
        </Style>
    </Page.Resources>
    <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto" />
            <RowDefinition Height="*" />
        </Grid.RowDefinitions>
        <StackPanel Grid.Row="0" Orientation="Horizontal"  Margin="0,40,0,0">
            <Button Name="GetMaxNumberButton" Click="GetMaxNumberButton_Click"  Content="Get Max Number" />
            <Button Name="GetMaxNumberAsyncButton" Click="GetMaxNumberAsyncButton_Click"  Content="Get Max Number Async" />
        </StackPanel>
        <StackPanel Grid.Row="1">
            <TextBox Name="TextBox1" AcceptsReturn="True" />
        </StackPanel>
    </Grid>

</Page>

MainPage.xaml.cs

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices.WindowsRuntime;
using Windows.Foundation;
using Windows.Foundation.Collections;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Controls.Primitives;
using Windows.UI.Xaml.Data;
using Windows.UI.Xaml.Input;
using Windows.UI.Xaml.Media;
using Windows.UI.Xaml.Navigation;
using Windows.Foundation.Diagnostics;
using System.Threading;
using System.Threading.Tasks;
using System.Collections.Concurrent;

// The Blank Page item template is documented at https://go.microsoft.com/fwlink/?LinkId=234238

namespace CpuUseDemo
{
    /// <summary>
    /// An empty page that can be used on its own or navigated to within a Frame.
    /// </summary>
    public sealed partial class MainPage : Page
    {
        public MainPage()
        {
            this.InitializeComponent();
        }


        const int NUM_TASKS = 48;
        const int MIN_ITERATIONS = int.MaxValue / 1000;
        const int MAX_ITERATIONS = MIN_ITERATIONS + 10000;

        long m_totalIterations = 0;
        readonly object m_totalItersLock = new object();

        private void GetMaxNumberButton_Click(object sender, RoutedEventArgs e)
        {
            GetMaxNumberAsyncButton.IsEnabled = false;
            lock (m_totalItersLock)
            {
                m_totalIterations = 0;
            }
            List<int> tasks = new List<int>();
            for (var i = 0; i < NUM_TASKS; i++)
            {
                var result = 0;
                result = GetNumber();
                tasks.Add(result);
            }
            var max = tasks.Max();
            var s = GetOutputString("GetMaxNumberButton_Click", NUM_TASKS, max, m_totalIterations);
            TextBox1.Text += s;
            GetMaxNumberAsyncButton.IsEnabled = true;
        }

        private async void GetMaxNumberAsyncButton_Click(object sender, RoutedEventArgs e)
        {
            GetMaxNumberButton.IsEnabled = false;
            GetMaxNumberAsyncButton.IsEnabled = false;
            lock (m_totalItersLock)
            {
                m_totalIterations = 0;
            }
            var tasks = new ConcurrentBag<Task<int>>();
            for (var i = 0; i < NUM_TASKS; i++)
            {
                tasks.Add(GetNumberAsync());
            }
            await Task.WhenAll(tasks.ToArray());
            var max = 0;
            foreach (var task in tasks)
            {
                max = Math.Max(max, task.Result);
            }
            var func = "GetMaxNumberAsyncButton_Click";
            var outputText = GetOutputString(func, NUM_TASKS, max, m_totalIterations);
            TextBox1.Text += outputText;
            this.GetMaxNumberButton.IsEnabled = true;
            GetMaxNumberAsyncButton.IsEnabled = true;
        }

        private int GetNumber()
        {
            var rand = new Random();
            var iters = rand.Next(MIN_ITERATIONS, MAX_ITERATIONS);
            var result = 0;
            lock (m_totalItersLock)
            {
                m_totalIterations += iters;
            }
            // we're just spinning here
            // and using Random to frustrate compiler optimizations
            for (var i = 0; i < iters; i++)
            {
                result = rand.Next();
            }
            return result;
        }

        private Task<int> GetNumberAsync()
        {
            return Task<int>.Run(() =>
            {
                return GetNumber();
            });
        }

        string GetOutputString(string func, int cycles, int max, long totalIters)
        {
            var fmt = "{0,-35}Tasks:{1,3}    Maximum:{2, 12}    Iterations:{3,12}\n";
            return String.Format(fmt, func, cycles, max, totalIters);
        }

    }
}