Поделиться через


Анализ использования ЦП в приложениях Магазина

Применимо к Windows и к Windows Phone

Когда вам требуется найти причину проблем с производительностью в своем приложении, лучше всего начать с понимания того, как именно приложение использует ЦП. Инструмент Использование ЦП в разделе "Производительность и диагностика" Visual Studio показывает, на что ЦП тратит время при выполнении кода C++, C#/VB и JavaScript. Чтобы сконцентрироваться на определенных сценариях, этот инструмент можно запустить вместе с инструментом Скорость реагирования ИП XAML и/или Расход энергии в рамках одного сеанса диагностики. В Visual Studio 2013 инструмент выборки циклов ЦП заменяется инструментом использования ЦП.

Примечание

Инструмент Использование ЦП нельзя использовать с приложениями Silverlight 8.1 Windows Phone.

В этом пошаговом руководстве рассматривается сбор и анализ данных об использовании ЦП для простого приложения.

Вы воспользуетесь стандартными процедурами раздела "Производительность и диагностика" для сбора данных, однако этот раздел предоставляет множество других возможностей для проведения сеанса диагностики и управления им. Например, вы можете запустить инструмент "Использование ЦП" в приложениях Магазина Windows Phone или Windows; а сеанс диагностики можно запустить на компьютере с Visual Studio, на устройстве Магазина Windows или Windows Phone, а также в одном из эмуляторов или имитаторов Visual Studio.

После этого вы подробно рассмотрите диагностический отчет по использованию ЦП.

Содержимое

Создание проекта ДемонстрацияИспользованияЦП

Что такое ДемонстрацияИспользованияЦП?

Сбор данных об использовании ЦП

Анализ отчета об использовании ЦП

Следующие шаги

MainPage.xaml

MainPage.xaml.cs

Создание проекта ДемонстрацияИспользованияЦП

  1. Создайте новый проект приложения Магазина Windows на C# с именем ДемонстрацияИспользованияЦП, используя шаблон ПустоеПриложение.

    Создание CpuUseDemoProject

  2. Замените MainPage.xaml этим кодом.

  3. Замените MainPage.xaml.cs этим кодом.

Что такое ДемонстрацияИспользованияЦП?

ДемонстрацияИспользованияЦП — это приложение, созданное для демонстрации сбора и анализа данных об использовании ЦП. Кнопки выдают число, вызывая метод, который выбирает максимальное значение из нескольких вызовов функции. Вызванная функция создает очень большое количество случайных значений, а затем возвращает последнее из них. Эти данные отображаются в текстовом поле. Выполните сборку приложения и попробуйте поработать с ним.

Пользовательский интерфейс CpuUseDemo

Это приложение не очень сложное, а его методы не слишком интересны, однако его достаточно для рассмотрения наиболее распространенных случаев анализа данных об использовании ЦП.

Сбор данных об использовании ЦП

Запуск сборки выпуска приложения в имитаторе

  1. В Visual Studio задайте Имитатор в качестве цели развертывания и Розничная в качестве конфигурации решения.

    • Выполнение приложения в имитаторе позволяет вам легко переключаться между приложением и интерфейсом IDE Visual Studio.

    • Выполнение этого приложения в режиме Выпуск позволяет вам получить более полное представление о фактической производительности приложения.

  2. В меню Отладка выберите Производительность и диагностика.

  3. В разделе "Производительность и диагностика" выберите Использование ЦП и затем Запуск.

    Запуск сеанса диагностики CpuUsage

  4. После запуска приложение щелкните Get Max Number (Получить максимальное значение). Подождите около секунды после вывода данных, а затем выберите Get Max Number Async (Получить максимальное значение в асинхронном режиме). Перерыв между нажатиями кнопок позволяет легче разделить соответствующие подпрограммы в диагностическом отчете.

  5. После вывода второй строки выберите Остановка сбора в разделе "Производительность и диагностика".

Остановка сбора данных CpuUsage

Инструмент "Использование ЦП" анализирует данные и отображает отчет.

Отчет CpuUsage

Анализ отчета об использовании ЦП

График зависимости использования ЦП от времени**|Выберите периоды временной шкалы для просмотра подробных сведений|Дерево вызовов для использования ЦП|Структура дерева вызовов|Внешний код|Столбцы данных дерева вызовов|**Асинхронные функции в дереве вызовов для использования ЦП

График зависимости использования ЦП от времени

График временной шкалы CpuUtilization (%)

На графике использования ЦП отображается активность ЦП для приложения в виде процента от совокупного времени ЦП, объединяющего все ядра процессора на устройстве. Данные для этого отчета были собраны на компьютере с двумя ядрами. Два больших всплеска активности ЦП соответствуют двум нажатиям кнопок. GetMaxNumberButton_Click выполняется синхронно на одном ядре, поэтому высота графика при таком методе никогда не превышает 50 %. GetMaxNumberAsycButton_Click выполняется асинхронно на обоих ядрах, поэтому график показывает, что использование ЦП близко к общей величине ресурсов обоих ядер.

Выберите периоды временной шкалы для просмотра подробных сведений

Используйте полосы выделения на временной шкале Диагностический сеанс, чтобы сконцентрироваться на данных GetMaxNumberButton_Click:

Выбрано GetMaxNumberButton_Click

Временная шкала Диагностический сеанс теперь отображает выбранный период (в данном отчете он немного превышает 2 секунды) и отфильтровывает дерево вызовов, отображая только те методы, которые выполнялись в рамках выбранного периода.

Теперь выберите период GetMaxNumberAsyncButton_Click.

Выбор отчета GetMaxNumberAsyncButton_Click

Этот метод выполняется примерно на одну секунду быстрее, чем GetMaxNumberButton_Click, однако значение записей в дереве вызовов не слишком очевидно.

Дерево вызовов для использования ЦП

Чтобы начать разбираться в сведениях дерева вызовов, снова выберите период GetMaxNumberButton_Click и просмотрите отображаемую для дерева вызовов информацию.

Структура дерева вызовов

Дерево вызовов GetMaxNumberButton_Click

Шаг 1

Узел верхнего уровня в деревьях вызовов для использования ЦП представляет собой псевдоузел

Шаг 2

В большинстве приложений при отключенном параметре Показать внешний код узлом второго уровня является узел [Внешний код], который содержит код системы и инфраструктуры, запускающий и останавливающий приложение, отрисовывающий пользовательский интерфейс, управляющий планированием потоков и предоставляющий приложению другие низкоуровневые службы.

Шаг 3

Дочерними элементами узла второго уровня являются методы пользовательского кода и асинхронные подпрограммы, которые вызываются или создаются кодом системы и инфраструктуры на втором уровне.

Шаг 4

Дочерние узлы метода содержат данные только для вызова родительского метода. Когда параметр Показать внешний код отключен, методы приложения также могут содержать узел [Внешний код].

Внешний код

Внешний код — это функции в компонентах системы и инфраструктуры, выполняемые создаваемым вами кодом. Внешний код включает в себя функции, которые запускают и останавливают приложение, отрисовывают пользовательский интерфейс, управляют потоками и предоставляют приложению другие низкоуровневые службы. В большинстве случаев вы не будете затрагивать внешний код, поэтому дерево вызовов для использования ЦП собирает внешние функции пользовательского метода в одном узле [Внешний код].

Если вы захотите посмотреть пути к вызовам внешнего кода, выберите Показать внешний код в списке Представление фильтра и выберите Применить.

Выберите "Представление фильтра", а затем "Показать внешний код"

Помните о том, что многие цепочки вызовов имеют глубокий уровень вложенности, поэтому ширина столбца "Имя функции" может превышать ширину многих мониторов. В этом случае имена функций отображаются в виде […]:

Вложенный внешний код в дереве вызовов

Используйте поле поиска, чтобы найти требуемый узел, а затем воспользуйтесь горизонтальной полосой прокрутки для отображения данных в представлении:

Поиск вложенного внешнего кода

Столбцы данных дерева вызовов

Общая активность ЦП (%)

Уравнение для общего процента

Процент активности ЦП приложения за выбранный период, затраченный на вызовы функции и вызванные ей функции. Обратите внимание, что это отличается от графика Использование ЦП, на котором сравнивается общая активность приложения за определенный период с общим доступным временем ЦП.

Собственная активность ЦП (%)

Уравнение Self %

Процент активности ЦП приложения за выбранный период, затраченный на вызовы функции без учета вызванных ей функций.

Общее время ЦП (мс)

Число миллисекунд, затраченных за выбранный период на вызовы функции и вызванные ей функции.

Собственное время ЦП (мс)

Число миллисекунд, затраченных за выбранный период на вызовы функции и вызванные ей функции.

Модуль

Имя модуля, содержащего функцию, или число модулей, содержащих функции, в узле [Внешний код].

Асинхронные функции в дереве вызовов для использования ЦП

Когда компилятор обнаруживает асинхронный метод, он создает скрытый класс для управления выполнением этого метода. По существу, класс — это конечный автомат, включающий в себя список созданных компилятором функций, которые вызывают операции исходного метода в асинхронном режиме, а также необходимые для их правильной работы обратные вызовы, планировщик и итераторы. При вызове исходного метода родительским методом среда выполнения удаляет метод из контекста выполнения родительского метода и выполняет методы скрытого класса в контексте кода системы и инфраструктуры, который управляет выполнением приложения. Асинхронные методы часто, хотя и не всегда, выполняются в одном или нескольких разных потоках. Этот код показан в дереве вызовов для использования ЦП в качестве дочернего для узла [Внешний код], расположенного сразу после верхнего узла дерева.

Чтобы увидеть его в нашем примере, снова выберите период GetMaxNumberAsyncButton_Click на временной шкале.

Выбор отчета GetMaxNumberAsyncButton_Click

Два первых узла в узле [Внешний код] представляют собой созданные компилятором методы класса конечного автомата. Третий является вызовом исходного метода. Развернув созданные методы, можно понять, как это работает.

Развернутое дерево вызовов GetMaxNumberAsyncButton_Click

  • MainPage::GetMaxNumberAsyncButton_Click не делает ничего сложного — он управляет списком значений задач, вычисляет максимальное значение из результатов и отображает выходные данные.

  • MainPage+<GetMaxNumberAsyncButton_Click>d__3::MoveNext показывает активность, требуемую для планирования и запуска 48 задач, использующих программу-оболочку для вызова GetNumberAsync.

  • MainPage::<GetNumberAsync>b__b показывает активность задач, вызывающих GetNumber.

Следующие шаги

Приложение ДемонстрацияИспользованияЦП нельзя назвать шедевром, но вы можете расширить его возможности, экспериментируя с асинхронными операциями и другими инструментами в разделе "Производительность и диагностика".

  • Обратите внимание на то, что MainPage::<GetNumberAsync>b__b проводит больше времени в узле [Внешний код], чем затрачивает на выполнение метода GetNumber. Основная часть этого времени является затратами на выполнение асинхронных операций. Попробуйте увеличить число задач (с помощью константы NUM_TASKS в MainPage.xaml.cs) и уменьшить число итераций в GetNumber (измените значение MIN_ITERATIONS). Запустите сценарий сбора и сравните активность ЦП MainPage::<GetNumberAsync>b__b с показаниями исходного диагностического сеанса инструмента "Использование ЦП". Попробуйте сократить число задач и увеличить количество итераций.

  • Часто пользователи вообще не заботятся о фактической производительности своего приложения — их больше волнует производительность и скорость реагирования, воспринимаемая пользователями. Инструмент "Скорость реагирования ИП XAML" отображает сведения об активности потока пользовательского интерфейса, влияющей на воспринимаемую скорость реагирования.

    Создайте новый сеанс в разделе "Производительность и диагностика" и добавьте как инструмент "Скорость реагирования ИП XAML", так и инструмент "Использование ЦП". Запустите сценарий сбора. Если вы дочитали до этого места, скорее всего, вы не нашли в отчете ничего нового, однако графики Использование потока пользовательского интерфейса для двух методов имеют весьма характерные различия. В сложных приложениях, используемых в реальных условиях, такое сочетание инструментов может оказаться очень полезным.

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

    }
}