Compartir a través de


Analizar el uso de CPU en aplicaciones de la Tienda

Se aplica a Windows y a Windows Phone

Si necesita investigar problemas de rendimiento de la aplicación, una buena forma de empezar es entender cómo utiliza la CPU. La herramienta Uso de CPU del hub Rendimiento y diagnósticos de Visual Studio muestra en qué partes emplea tiempo la CPU al ejecutar código C++, C#/VB y JavaScript. Para centrarse en escenarios concretos, Uso de CPU se puede ejecutar con las herramientas Capacidad de respuesta de la IU de XAML, Consumo de energía o con ambas en una única sesión de diagnóstico. La herramienta Uso de CPU sustituye a la herramienta Muestreo de la CPU en Visual Studio 2013.

Nota

La herramienta Uso de CPU no se puede usar con aplicaciones de Windows Phone Silverlight 8.1.

Este tutorial le guía por la recopilación y el análisis de datos de Uso de CPU de una aplicación sencilla.

Usará los procedimientos predeterminados del hub Rendimiento y diagnósticos para recopilar los datos, aunque este hub ofrece muchas más opciones para ejecutar y administrar la sesión de diagnóstico. Por ejemplo, puede ejecutar la herramienta Uso de CPU en aplicaciones de la Tienda de Windows Phone o la Tienda Windows; ejecutar la sesión de diagnóstico en el equipo de Visual Studio, en un dispositivo de la Tienda de Windows Phone o de la Tienda Windows o en uno de los emuladores o simuladores de Visual Studio.

A continuación examinará en profundidad el informe de diagnóstico de Uso de CPU.

Contenido

Crear el proyecto CpuUseDemo

Qué es CpuUseDemo

Recopilar datos de Uso de CPU

Analizar el informe de Uso de CPU

Pasos siguientes

MainPage.xaml

MainPage.xaml.cs

Crear el proyecto CpuUseDemo

  1. Cree un nuevo proyecto de aplicación de C# de la Tienda Windows llamado CpuUseDemo mediante la plantilla BlankApp.

    Crear CpuUseDemoProject

  2. Sustituya MainPage.xaml por este código.

  3. Sustituya MainPage.xaml.cs por este código.

Qué es CpuUseDemo

CpuUseDemo es una aplicación creada para mostrar cómo recopilar y analizar datos de Uso de CPU. Los botones generan un número al llamar a un método que selecciona el valor máximo de varias llamadas a una función. La función llamada crea un número muy alto de valores aleatorios y luego devuelve el último. Los datos se muestran en un cuadro de texto. Cree la aplicación y pruébela.

Interfaz de usuario de CpuUseDemo

No se trata de una aplicación muy emocionante y los métodos de CpuUseDemo no son muy interesantes, pero es lo suficientemente sencilla para mostrar algunos casos comunes de análisis de datos de Uso de CPU.

Recopilar datos de Uso de CPU

Ejecutar una compilación de la versión de la aplicación en el simulador

  1. En Visual Studio, establezca el destino de implementación en Simulador y la configuración de la solución en Comercial.

    • La ejecución de la aplicación en el simulador le permite alternar fácilmente entre la aplicación y el IDE de Visual Studio.

    • La ejecución de esta aplicación en modo Versión le ofrece una mejor visión del rendimiento real de la aplicación.

  2. En el menú Depurar, elige Rendimiento y diagnósticos.

  3. En el hub Rendimiento y diagnósticos, elija Uso de CPU y luego Iniciar.

    Iniciar la sesión de diagnóstico de CpuUsage

  4. Cuando se inicie la aplicación, haga clic en Obtener número máximo. Cuando se muestre el resultado, espere aproximadamente un segundo y luego elija Obtener número máximo de asincronías. La espera entre clics de botón facilita el aislamiento de las rutinas de clic de botón en el informe de diagnóstico.

  5. Cuando aparezca la segunda línea de resultados, elija Detener recolección en el hub Rendimiento y diagnósticos.

Detener la recolección de datos de CpuUsage

La herramienta Uso de CPU analiza y muestra el informe.

Informe de CpuUsage

Analizar el informe de Uso de CPU

Gráfico de escala de tiempo Uso de CPU**|Seleccionar segmentos de la escala de tiempo para ver detalles|Árbol de llamadas de Uso de CPU|Estructura del árbol de llamadas|Código externo|Columnas de datos del árbol de llamadas|**Funciones asincrónicas en el árbol de llamadas de Uso de CPU

Gráfico de escala de tiempo Uso de CPU

Gráfico de escala de tiempo (%) de CpuUtilization

El gráfico Uso de CPU muestra la actividad de CPU de la aplicación como un porcentaje del tiempo total de CPU de todos los núcleos de procesador del dispositivo. Los datos de este informe se recopilaron en un equipo de doble núcleo. Los dos grandes picos representan la actividad de CPU de los dos clic de botón. GetMaxNumberButton_Click se ejecuta de forma sincrónica en un único núcleo, así que tiene sentido que la altura del gráfico del método nunca supere el 50 %. GetMaxNumberAsycButton_Click se ejecuta de forma asincrónica en ambos núcleos, así que una vez más parece correcto que el pico se acerque a la utilización de todos los recursos de la CPU en ambos núcleos.

Seleccionar segmentos de la escala de tiempo para ver detalles

Use las barras de selección de la escala de tiempo Sesión de diagnóstico para centrarse en los datos de GetMaxNumberButton_Click:

GetMaxNumberButton_Click seleccionado

La escala de tiempo Sesión de diagnóstico ahora muestra el tiempo empleado en el segmento seleccionado (un poco más de 2 segundos en este informe) y filtra el árbol de llamadas para mostrar solo esos métodos que se ejecutaron en la selección.

Ahora seleccione el segmento GetMaxNumberAsyncButton_Click.

Selección de informes GetMaxNumberAsyncButton_Click

Este método se completa aproximadamente un segundo más rápido que GetMaxNumberButton_Click, pero el significado de las entradas del árbol de llamadas es menos obvio.

Árbol de llamadas de Uso de CPU

Para empezar a entender la información del árbol de llamadas, vuelva a seleccionar el segmento GetMaxNumberButton_Click y examine los detalles del árbol de llamadas.

Estructura del árbol de llamadas

Árbol de llamadas GetMaxNumberButton_Click

Paso 1

El nodo de nivel superior de los árboles de llamadas de Uso de CPU es un pseudonodo

Paso 2

En la mayoría de las aplicaciones, si la opción Mostrar código externo está deshabilitada, el nodo de segundo nivel es un nodo [Código externo] que contiene el código del sistema y Framework que inicia y detiene la aplicación, dibuja la IU, controla la programación de subprocesos y ofrece otros servicios de bajo nivel a la aplicación.

Paso 3

Los elementos secundarios del nodo de segundo nivel son los métodos de código de usuario y las rutinas asíncronas llamados o creados por el sistema de segundo nivel y el código de Framework.

Paso 4

Los nodos secundarios de un método contienen datos únicamente de las llamadas del método principal. Si Mostrar código externo está deshabilitado, los métodos de la aplicación también pueden contener un nodo [Código externo].

Código externo

El código externo son funciones de los componentes del sistema y Framework ejecutados por el código que escribe. El código externo incluye funciones que inician y detienen la aplicación, dibujan la IU, controlan los subprocesos y ofrecen otros servicios de bajo nivel a la aplicación. En la mayoría de los casos, no le interesará el código externo, por lo que el árbol de llamadas de Uso de CPU recopila las funciones externas de un método de usuario en un nodo [Código externo].

Si quiere ver las rutas de acceso a las llamadas de código externo, elija Mostrar código externo en la lista Vista de filtro y luego Aplicar.

Elegir Vista de filtro, después Mostrar código externo

Tenga en cuenta que muchas cadenas de llamadas de código externo están profundamente anidadas, así que el ancho de la columna Nombre de la función puede superar el ancho de pantalla de todos los monitores, excepto de los más grandes. Si ese es el caso, los nombres de función se muestran como […]:

Código externo anidado en el árbol de llamadas

Use el cuadro de búsqueda para localizar un nodo que esté buscando y luego use la barra de desplazamiento horizontal para visualizar los datos:

Buscar código externo anidado

Columnas de datos del árbol de llamadas

CPU total (%)

Ecuación de datos % total

Porcentaje de la actividad de CPU de la aplicación en el intervalo temporal seleccionado usado por las llamadas a la función y las funciones llamadas por la función. Tenga en cuenta que es diferente al gráfico de escala de tiempo Uso de CPU, que compara la actividad total de la aplicación en un intervalo temporal con el total de capacidad de CPU disponible.

Solo CPU (%)

Autoecuación %

Porcentaje de la actividad de CPU de la aplicación en el intervalo temporal seleccionado usado por las llamadas a la función, sin incluir la actividad de las funciones llamadas por la función.

CPU total (ms)

Número de milisegundos empleado en las llamadas a la función en el intervalo temporal seleccionado y las funciones llamadas por la función.

Solo CPU (ms)

Número de milisegundos empleado en las llamadas a la función en el intervalo temporal seleccionado y las funciones llamadas por la función.

Módulo

Nombre del módulo que contiene la función o número de módulos que contienen las funciones en un nodo [Código externo].

Funciones asíncronas en el árbol de llamadas de Uso de CPU

Cuando el compilador encuentra un método asíncrono, crea una clase oculta para controlar su ejecución. Conceptualmente, la clase es una máquina de estados que incluye una lista de funciones generadas por el compilador que llaman a operaciones del método original de forma asíncrona, así como a las devoluciones de llamada, el programador y los iteradores que necesitan correctamente. Cuando un método principal llama al método original, el tiempo de ejecución quita al método del contexto de ejecución del elemento principal y ejecuta los métodos de la clase oculta en el contexto del código del sistema y Framework que controla la ejecución de la aplicación. Los métodos asíncronos suelen ejecutarse, aunque no siempre, en uno o varios subprocesos diferentes. Este código se muestra en el árbol de llamadas de Uso de CPU como elemento secundario del nodo [Código externo] inmediatamente debajo del nodo superior del árbol.

Para verlo en el ejemplo, vuelva a seleccionar el segmento GetMaxNumberAsyncButton_Click en la escala de tiempo.

Selección de informes GetMaxNumberAsyncButton_Click

Los dos primeros nodos bajo [Código externo] son los métodos generados por el compilador de la clase de la máquina de estados. El tercero es la llamada al método original. Al expandir los métodos generados se muestra lo que está sucediendo.

Árbol de llamadas GetMaxNumberAsyncButton_Click expandido

  • MainPage::GetMaxNumberAsyncButton_Click hace muy poco; administra una lista de los valores de la tarea, calcula el máximo de los resultados y los muestra.

  • MainPage+<GetMaxNumberAsyncButton_Click>d__3::MoveNext muestra la actividad necesaria para programar y lanzar las 48 tareas que incluyen la llamada a GetNumberAsync.

  • MainPage::<GetNumberAsync>b__b muestra la actividad de las tareas que llaman a GetNumber.

Pasos siguientes

La aplicación CpuUseDemo no es la más brillante de las aplicaciones, pero puede aumentar su utilidad si la usa para experimentar con el funcionamiento asíncrono y otras herramientas del hub Rendimiento y diagnósticos.

  • Tenga en cuenta que MainPage::<GetNumberAsync>b__b emplea más tiempo en [Código externo] que en la ejecución del método GetNumber. La mayor parte de este tiempo es la sobrecarga de las operaciones asíncronas. Intente aumentar el número de tareas (establecido en la constante NUM_TASKS de MainPage.xaml.cs) y reducir el número de iteraciones de GetNumber (cambie el valor MIN_ITERATIONS). Ejecute el escenario de recopilación y compare la actividad de CPU de MainPage::<GetNumberAsync>b__b con la de la sesión de diagnóstico original de Uso de CPU. Intente reducir las tareas y aumentar las iteraciones.

  • A los usuarios no les suele preocupar el rendimiento real de la aplicación; les importa su rendimiento y capacidad de respuesta percibidos. La herramienta Capacidad de respuesta de la IU de XAML muestra detalles de actividad en el subproceso de la IU que afecta a la capacidad de respuesta percibida.

    Cree una nueva sesión en el hub Rendimiento y diagnósticos y agregue las herramientas Capacidad de respuesta de la IU de XAML y Uso de CPU. Ejecute el escenario de recopilación. Si ha leído hasta aquí, el informe probablemente no le diga nada que ya no haya descubierto, pero las diferencias en el gráfico de escala de tiempo Utilización del subproceso de IU de los dos métodos es impactante. En aplicaciones complejas del mundo real, la combinación de herramientas puede resultar muy ú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);
        }

    }
}