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


Практическое руководство по оптимизации кода и сокращению затрат на вычисления (C#, Visual Basic, C++, F#)

Сокращение времени вычислений означает сокращение затрат, поэтому оптимизация кода может сэкономить деньги. В этом примере используется пример приложения с проблемами производительности, чтобы продемонстрировать, как использовать средства профилирования для повышения эффективности. Если вы хотите сравнить средства профилирования, см. Какой инструмент следует выбрать?

В этом примере рассматриваются следующие темы:

  • Важность оптимизации кода и ее влияние на снижение затрат на вычислительные ресурсы.
  • Как использовать средства профилирования Visual Studio для анализа производительности приложений.
  • Как интерпретировать данные, предоставляемые этими средствами, для выявления узких мест производительности.
  • Применение практических стратегий для оптимизации кода, акцент на использовании ЦП, выделении памяти и взаимодействии с базами данных.

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

Пример оптимизации

Пример приложения, рассмотренного в этом примере, — это приложение .NET, которое выполняет запросы к базе данных блогов и записей блогов. Он использует Entity Framework, популярную технологию ORM (Object-Relational Mapping) для .NET, для взаимодействия с локальной базой данных SQLite. Приложение структурировано для выполнения большого количества запросов, имитируя реальный сценарий, в котором приложение .NET может потребоваться для обработки обширных задач извлечения данных. Образец приложения является изменённой версией ознакомительного примера по началу работы с Entity Framework.

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

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

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

  • затраты на взаимодействие с базой данных: приложения, выполняющие большое количество запросов к базе данных, могут столкнуться с узкими местами, связанными с взаимодействием с базой данных. Это включает в себя неэффективные запросы, чрезмерные вызовы базы данных и плохое использование возможностей Entity Framework, все из которых могут снизить производительность.

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

Вызов

Устранение проблем с производительностью в примере приложения .NET представляет несколько проблем. Эти проблемы связаны с сложностью диагностики узких мест производительности. Основные проблемы, связанные с устранением проблем, описанных ниже.

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

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

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

Стратегия

Ниже приведено высокоуровневое представление подхода в этом примере:

  • Мы начинаем расследование, снимая трассировку использования центрального процессора. Инструмент использования ЦП Visual Studio часто полезен для начала исследований производительности и оптимизации кода с целью снижения затрат на вычислительные ресурсы.
  • Затем, чтобы получить дополнительные аналитические сведения, чтобы изолировать проблемы или повысить производительность, мы собираем трассировку с помощью одного из других средств профилирования. Например:
    • Мы рассмотрим использование памяти. Для .NET сначала мы используем средство выделения объектов .NET . (Для .NET или C++вместо этого можно просмотреть средство использования памяти.)
    • Для ADO.NET или Entity Framework можно использовать средство базы данных для изучения запросов SQL, точного времени запроса и многого другого.

Для сбора данных требуются следующие задачи:

  • Настройка приложения на релизную сборку.
  • Выбор средства использования ЦП в профилировщике производительности (ALT+F2). (Более поздние шаги включают несколько других инструментов.)
  • В профилировщике производительности запустите приложение и соберите трассировку.

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

После сбора трассировки с помощью инструмента использования ЦП и загрузки её в Visual Studio, сначала проверьте первоначальную страницу отчета .diagsession, в которой показаны суммированные данные. Используйте ссылку Open details в отчете.

Скриншот со сведениями об использовании ЦП.

В представлении сведений отчета откройте представление дерева вызовов . Путь кода с наибольшим использованием ЦП в приложении называется горячим путем. Значок пламени Hot Path (снимок экрана со значком ) может помочь быстро определить проблемы с производительностью, которые можно улучшить.

В представлении дерева вызовов можно увидеть высокую загрузку ЦП для метода GetBlogTitleX в приложении, используя около 60% общий объем использования ЦП приложения. Однако значение самообслуживания ЦП для GetBlogTitleX низкое, только около 10%. В отличие от Всего ЦП, значение Собственного ЦП исключает время, затраченное на другие функции, поэтому нам нужно изучить более глубоко дерево вызовов, чтобы найти реальное узкое место.

снимок экрана представления дерева вызовов в инструменте использования ЦП.

GetBlogTitleX выполняет внешние вызовы к двум LINQ DLL, которые используют большую часть времени процессора, как это видно по очень высоким значениям Сам процессор. Это первый ключ, что запрос LINQ может быть областью для оптимизации.

снимок экрана вида

Чтобы получить визуальное дерево вызовов и другое представление данных, откройте представление диаграммы пламени. (Или щелкните правой кнопкой мыши GetBlogTitleX и выберите Просмотр в Flame Graph.) Здесь снова похоже, что метод GetBlogTitleX отвечает за значительное использование ЦП приложением (показано желтым цветом). Внешние вызовы библиотек DLL LINQ отображаются под полем GetBlogTitleX, и они используют все время ЦП для метода.

снимок экрана представления

Сбор дополнительных данных

Часто другие средства могут предоставить дополнительную информацию, чтобы помочь анализу и изолировать проблему. В этом примере мы рассмотрим следующий подход:

  • Сначала рассмотрим использование памяти. Может существовать корреляция между высокой загрузкой ЦП и высоким использованием памяти, поэтому может быть полезно изучить оба аспекта для выявления проблемы.
  • Поскольку мы определили библиотеки DLL LINQ, мы также рассмотрим инструмент работы с базами данных.

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

Чтобы узнать, что происходит с приложением с точки зрения использования памяти, мы собираем трассировку с помощью средства выделения объектов .NET (для C++, вы можете использовать средство использования памяти вместо него). В представлении дерева вызовов в трассировке памяти отображается горячий путь и помогает определить область с высоким использованием памяти. На этом этапе не удивительно, что метод GetBlogTitleX, как представляется, создает много объектов! На самом деле более 900 000 выделений объектов.

снимок экрана: представление дерева вызовов в средстве выделения объектов .NET.

Большинство созданных объектов — это строки, массивы объектов и Int32s. Мы можем увидеть, как эти типы создаются, проверив исходный код.

Проверка запроса в средстве "База данных"

В профилировщике производительности мы выбираем средство базы данных вместо использования ЦП (или выберите оба). Когда мы соберем трассировку, откройте вкладку Запросов на странице диагностики. На вкладке "Запросы" для трассировки базы данных отображается первая строка с самым длинным запросом 2446 мс. В столбце записей показано, сколько записей считывает запрос. Эти сведения можно использовать для последующего сравнения.

снимок экрана: запросы базы данных в средстве

Проверив инструкцию SELECT, созданную LINQ в столбце запроса, мы определяем первую строку как запрос, связанный с методом GetBlogTitleX. Чтобы просмотреть полную строку запроса, разверните ширину столбца. Полная строка запроса:

SELECT "b"."Url", "b"."BlogId", "p"."PostId", "p"."Author", "p"."BlogId", "p"."Content", "p"."Date", "p"."MetaData", "p"."Title"
FROM "Blogs" AS "b" LEFT JOIN "Posts" AS "p" ON "b"."BlogId" = "p"."BlogId" ORDER BY "b"."BlogId"

Обратите внимание, что приложение извлекает здесь много значений столбцов, возможно, больше, чем нам нужно. Рассмотрим исходный код.

Оптимизация кода

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

foreach (var blog in db.Blogs.Select(b => new { b.Url, b.Posts }).ToList())
  {
    foreach (var post in blog.Posts)
    {
      if (post.Author == "Fred Smith")
      {
        Console.WriteLine($"Post: {post.Title}");
      }
  }
}

Этот код использует циклы foreach для поиска базы данных для любых блогов с "Фред Смит" в качестве автора. Глядя на это, вы можете увидеть, что многие объекты создаются в памяти: новый массив объектов для каждого блога в базе данных, связанные строки для каждого URL-адреса и значения свойств, содержащихся в записях, таких как идентификатор блога.

Мы делаем небольшое исследование и находим некоторые распространенные рекомендации по оптимизации запросов LINQ. Кроме того, мы можем сэкономить время и позволить Copilot сделать исследования для нас.

Если мы используем Copilot, выберите Ask Copilot в контекстном меню и введите следующий вопрос:

Can you make the LINQ query in this method faster?

Совет

Для формирования хороших вопросов для Copilot можно использовать такие команды с косой чертой, как /optimize.

В этом примере Copilot предоставляет следующие предложенные изменения кода, а также объяснение.

public void GetBlogTitleX()
{
    var posts = db.Posts
        .Where(post => post.Author == "Fred Smith")
        .Select(post => post.Title)
        .ToList();

    foreach (var postTitle in posts)
    {
        Console.WriteLine($"Post: {postTitle}");
    }
}

Этот код содержит несколько изменений, которые помогут оптимизировать запрос:

  • Добавлено предложение Where и устранён один из циклов foreach.
  • В инструкции Select проецируется только свойство Title, и это все, что нам нужно в этом примере.

Затем мы повторно проверим, используя средства профилирования.

Результаты

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

снимок экрана с улучшенным использованием ЦП в представлении

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

снимок экрана: улучшенное использование ЦП в представлении

Проверьте результаты трассировки в инструменте базы данных, и только две записи были считаны с помощью этого запроса вместо 100 000! Кроме того, запрос значительно упрощается и устраняет ненужные ранее созданные левые соединения.

снимок экрана с более быстрым временем запроса в средстве

Затем мы заново проверяем результаты в средстве выделения объектов .NET и увидим, что GetBlogTitleX отвечает только за выделение 56 000 объектов, что почти на 95% меньше, чем 900 000!

снимок экрана с уменьшенными выделениями памяти в инструменте выделения объектов .NET.

Итерировать

Может потребоваться несколько оптимизаций, и мы можем продолжить итерацию изменений кода, чтобы увидеть, какие изменения повышают производительность и помогают сократить затраты на вычисления.

Дальнейшие действия

В следующих статьях и блогах содержатся дополнительные сведения, которые помогут вам эффективно использовать средства производительности Visual Studio.