Condividi tramite


Analizzare l'utilizzo della CPU nelle app dello Store

Si applica a Windows e Windows Phone

Quando devi analizzare problemi di prestazioni nella tua app, un buon punto di partenza è comprendere in che modo l'app usa la CPU. Lo strumento Utilizzo CPU nell'hub Prestazioni e diagnostica di Visual Studio indica i punti in cui la CPU impiega più tempo per l'esecuzione di codice C++, C#/VB e JavaScript. Per concentrare l'attenzione su scenari specifici, lo strumento Utilizzo CPU può essere eseguito insieme allo strumento Velocità di risposta interfaccia utente XAML o allo strumento Utilizzo di energia oppure insieme a entrambi gli strumenti in un'unica sessione di diagnostica. Lo strumento Utilizzo CPU sostituisce lo strumento Campionamento CPU in Visual Studio 2013.

Nota

Lo strumento Utilizzo CPU non può essere usato con le app Silverlight per Windows Phone 8.1.

Questa procedura dettagliata ti assiste nel processo di raccolta e analisi dei dati di Utilizzo CPU per un'app semplice.

Userai le procedure predefinite dell'hub Prestazioni e diagnostica per raccogliere i dati, ma l'hub ti offre molte altre opzioni per eseguire e gestire la sessione di diagnostica. Puoi ad esempio usare lo strumento Utilizzo CPU per app per Windows Phone o di Windows Store ed eseguire la sessione di diagnostica nel computer che esegue Visual Studio, in un dispositivo Windows Phone o Windows Store oppure in uno degli emulatori o simulatori di Visual Studio.

Analizzerai quindi in modo approfondito il report diagnostico di Utilizzo CPU.

Contenuto

Creare il progetto CpuUseDemo

Che cos'è CpuUseDemo?

Raccogliere i dati di Utilizzo CPU

Analizzare il report di Utilizzo CPU

Passaggi successivi

MainPage.xaml

MainPage.xaml.cs

Creare il progetto CpuUseDemo

  1. Crea un nuovo progetto di app di Windows Store C# denominato CpuUseDemo usando il modello Applicazione vuota.

    Creare il progetto CpuUseDemoProject

  2. Sostituisci MainPage.xaml con questo codice.

  3. Sostituisci MainPage.xaml.cs con questo codice.

Che cos'è CpuUseDemo?

CpuUseDemo è un'app creata per illustrare come raccogliere e analizzare i dati di Utilizzo CPU. I pulsanti generano un numero chiamando un metodo che seleziona il valore massimo da più chiamate a una funzione. La funzione chiamata crea un numero molto elevato di valori casuali e restituisce l'ultimo. I dati sono visualizzati in una casella di testo. Compila l'app e prova a usarla.

Interfaccia utente di CpuUseDemo

L'app non è molto appassionante e i metodi di CpuUseDemo non sono molto interessanti, ma si tratta di un modo abbastanza semplice per illustrare alcuni casi comuni di analisi dei dati di Utilizzo CPU.

Raccogliere i dati di Utilizzo CPU

Eseguire una build di rilascio dell'app nel simulatore

  1. In Visual Studio imposta la destinazione della distribuzione su Simulatore e la configurazione della soluzione su Versione finale.

    • L'esecuzione dell'app nel simulatore ti consente di passare in modo semplice tra l'app e l'IDE di Visual Studio.

    • L'esecuzione dell'app in modalità Release ti offre un punto di vista migliore sulle prestazioni effettive dell'app.

  2. Scegli Prestazioni e diagnostica dal menu Debug.

  3. Nell'hub Prestazioni e diagnostica scegli Utilizzo CPU e quindi Avvio.

    Avviare la sessione di diagnostica di CpuUsage

  4. All'avvio dell'app, fai clic su Get Max Number. Attendi circa un secondo dopo la visualizzazione dell'output, quindi scegli Get Max Number Async. L'attesa tra i clic sui pulsanti consente di isolare in modo più semplice le routine relative ai clic nel report di diagnostica.

  5. Dopo la visualizzazione della seconda riga di output, scegli Arresta raccolta nell'hub Prestazioni e diagnostica.

Arresto della raccolta dei dati di CpuUsage

Lo strumento Utilizzo CPU analizza i dati e visualizza il report.

Report di CpuUsage

Analizzare il report di Utilizzo CPU

Grafico della sequenza temporale dell'utilizzo della CPU**|Selezionare i segmenti della sequenza temporale per visualizzare i dettagli|Albero delle chiamate di Utilizzo CPU|Struttura dell'albero delle chiamate|Codice esterno|Colonne di dati dell'albero delle chiamate|**Funzioni asincrone nell'albero delle chiamate di Utilizzo CPU

Grafico della sequenza temporale dell'utilizzo della CPU

Grafico della sequenza temporale CpuUtilization (%)

Il grafico dell'utilizzo della CPU mostra l'attività della CPU dell'app sotto forma di percentuale di tutto il tempo CPU per tutti i core del processore nel dispositivo. I dati di questo report sono stati raccolti in una macchina dual core. I due grossi picchi rappresentano l'attività della CPU per i due clic sui pulsanti. Il metodo GetMaxNumberButton_Click viene eseguito in modo sincrono su un singolo core, quindi è corretto che l'altezza del grafico non superi mai il 50%. Il metodo GetMaxNumberAsycButton_Click viene eseguito in modo asincrono tra i due core, quindi anche in questo caso è corretto che il picco si avvicini all'utilizzo di tutte le risorse della CPU su entrambi i core.

Selezionare i segmenti della sequenza temporale per visualizzare i dettagli

Usa le barre di selezione nella sequenza temporale Sessione di diagnostica per analizzare i dati relativi a GetMaxNumberButton_Click:

GetMaxNumberButton_Click selezionato

La sequenza temporale Sessione di diagnostica mostra ora il tempo trascorso nel segmento selezionato (poco più di due secondi in questo report) e l'albero delle chiamate viene filtrato per mostrare solo i metodi eseguiti nella selezione.

Seleziona ora il segmento GetMaxNumberAsyncButton_Click.

Selezione del report per GetMaxNumberAsyncButton_Click

Questo metodo viene completato in circa un secondo in meno rispetto a GetMaxNumberButton_Click, ma il significato delle voci dell'albero delle chiamate è meno ovvio.

Albero delle chiamate di Utilizzo CPU

Per iniziare a comprendere le informazioni dell'albero delle chiamate, seleziona di nuovo il segmento GetMaxNumberButton_Click e analizza i dettagli dell'albero.

Struttura dell'albero delle chiamate

Albero delle chiamate GetMaxNumberButton_Click

Passaggio 1

Il nodo di livello principale nell'albero delle chiamate di Utilizzo CPU è uno pseudo-nodo

Passaggio 2

Nella maggior parte delle app, quando l'opzione Mostra codice esterno è disabilitata, il nodo di secondo livello è un nodo [Codice esterno] contenente il codice di sistema e di framework che avvia e arresta l'app, disegna l'interfaccia utente, controlla la pianificazione dei thread e fornisce altri servizi di basso livello all'app.

Passaggio 3

Gli elementi figlio del nodo di secondo livello sono i metodi del codice utente e le routine asincrone che vengono chiamati o creati dal codice di sistema o di framework di secondo livello.

Passaggio 4

I nodi figlio di un metodo contengono i dati solo per le chiamate del metodo padre. Quando l'opzione Mostra codice esterno è disabilitata, i metodi dell'app possono contenere anche un nodo [Codice esterno].

Codice esterno

Il codice esterno rappresenta funzioni nei componenti del sistema e del framework che vengono eseguite dal codice scritto. Il codice esterno include funzioni che avviano e arrestano l'app, disegnano l'interfaccia utente, controllano il threading e forniscono altri servizi di basso livello all'app. Nella maggior parte dei casi non sarai interessato al codice esterno e per questo l'albero delle chiamate di Utilizzo CPU raccoglie le funzioni esterne di un metodo utente in un unico nodo [Codice esterno].

Se vuoi visualizzare i percorsi delle chiamate del codice esterno, scegli Mostra codice esterno nell'elenco Visualizzazione filtro e quindi scegli Applica.

Scegliere Visualizzazione filtro, quindi Mostra codice esterno

Tieni presente che numerose catene di chiamate del codice esterno sono molto annidate, pertanto la larghezza della colonna Nome funzione può superare la larghezza di visualizzazione in quasi tutti i monitor, ad eccezione di quelli più grandi. Quando ciò si verifica, i nomi delle funzioni sono visualizzati come […]:

Codice esterno annidato nell'albero delle chiamate

Usa la casella di ricerca per trovare il nodo che stai cercando, quindi usa la barra di scorrimento orizzontale per visualizzare i dati:

Ricerca di codice esterno annidato

Colonne di dati dell'albero delle chiamate

CPU totale (%)

Equazione % dati totali

Percentuale dell'attività CPU dell'app nell'intervallo di tempo selezionato usata dalle chiamate alla funzione e dalle funzioni chiamate dalla funzione. Osserva che si tratta di un valore diverso da quello del grafico della sequenza temporale di Utilizzo CPU, che mette a confronto l'attività totale dell'app in un intervallo di tempo con la capacità CPU disponibile totale.

CPU auto (%)

Equazione % automatica

Percentuale dell'attività CPU dell'app nell'intervallo di tempo selezionato usata dalle chiamate alla funzione, esclusa l'attività delle funzioni chiamate dalla funzione.

CPU totale (ms)

Numero di millisecondi usati nelle chiamate alla funzione nell'intervallo di tempo selezionato e per le funzioni chiamate dalla funzione.

CPU auto (ms)

Numero di millisecondi usati nelle chiamate alla funzione nell'intervallo di tempo selezionato e per le funzioni chiamate dalla funzione.

Modulo

Nome del modulo contenente la funzione o numero dei moduli contenenti le funzioni in un nodo [Codice esterno].

Funzioni asincrone nell'albero delle chiamate di Utilizzo CPU

Quando il compilatore incontra un metodo asincrono, crea una classe nascosta per controllare l'esecuzione del metodo. Concettualmente, la classe è una macchina a stati che include un elenco di funzioni generate dal compilatore che chiamano le operazioni del metodo originale in modo asincrono, nonché i callback, l'utilità di pianificazione e gli iteratori necessari. Quando il metodo originale viene chiamato da un metodo padre, il runtime rimuove il metodo dal contesto di esecuzione del padre ed esegue i metodi della classe nascosta nel contesto del codice di sistema e di framework che controllano l'esecuzione dell'app. Spesso, ma non sempre, i metodi asincroni vengono eseguiti in uno o più thread diversi. Il codice è illustrato nell'albero delle chiamate di Utilizzo CPU come figlio del nodo [Codice esterno] immediatamente sotto il nodo principale dell'albero.

Per vedere questo aspetto nel nostro esempio, seleziona di nuovo il segmento GetMaxNumberAsyncButton_Click nella sequenza temporale.

Selezione del report per GetMaxNumberAsyncButton_Click

I primi due nodi sotto [Codice esterno] sono i metodi generati dal compilatore della classe macchina a stati. Il terzo è la chiamata al metodo originale. Espandendo i metodi generati puoi vedere ciò che succede.

Albero delle chiamate GetMaxNumberAsyncButton_Click espanso

  • MainPage::GetMaxNumberAsyncButton_Click fa molto poco: gestisce un elenco dei valori delle attività, calcola il massimo dei risultati e mostra l'output.

  • MainPage+<GetMaxNumberAsyncButton_Click>d__3::MoveNext mostra l'attività necessaria per pianificare e avviare le 48 attività che eseguono il wrapping della chiamata a GetNumberAsync.

  • MainPage::<GetNumberAsync>b__b mostra le operazioni eseguite dalle attività che chiamano GetNumber.

Passaggi successivi

L'app CpuUseDemo non è la più brillante delle app, ma puoi estenderne l'utilità usandola per provare l'operazione asincrona e altri strumenti nell'hub Prestazioni e diagnostica.

  • Osserva che MainPage::<GetNumberAsync>b__b usa più tempo in [Codice esterno] rispetto a quello usato per l'esecuzione del metodo GetNumber. Gran parte di questo tempo è correlato al sovraccarico delle operazioni asincrone. Prova ad aumentare il numero di attività (impostato nella costante NUM_TASKS di MainPage.xaml.cs) e a ridurre il numero di iterazioni in GetNumber (modifica il valore di MIN_ITERATIONS). Esegui lo scenario di raccolta e confronta l'attività della CPU di MainPage::<GetNumberAsync>b__b con quella della sessione di diagnostica originale di Utilizzo CPU. Prova a ridurre le attività e ad aumentare le iterazioni.

  • Gli utenti spesso non danno importanza alle reali prestazioni della tua app, bensì alle prestazioni percepite e alla velocità di risposta dell'app. Lo strumento Velocità di risposta interfaccia utente XAML mostra i dettagli dell'attività nel thread UI che influiscono sulla velocità di risposta percepita.

    Crea una nuova sessione nell'hub Prestazioni e diagnostica e aggiungi sia lo strumento Velocità di risposta interfaccia utente XAML sia lo strumento Utilizzo CPU. Esegui lo scenario di raccolta. Se sei arrivato a leggere fino a questo punto, probabilmente il report non ti dirà niente che tu non ti sia già immaginato, ma le differenze nel grafico della sequenza temporale di Utilizzo thread UI per i due metodi sono notevoli. Con le app complesse reali, la combinazione di strumenti può essere molto utile.

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);
        }

    }
}