Измерение производительности приложения

Измерение производительности приложения c BenchmarkDotNet

Последнее обновление: 22.01.2023

Нередко возникает необходимость узнать некоторые аспекты производительности программы, такие как скорость выполнения, количество потребляемой памяти и ряд других, либо сравнить производительность ряда альтернативных решений и выбрать из них наилучшее. В C# для этой цели можно использовать библиотеку BenchmarkDotNet. Офиицальный сайт библиотеки https://benchmarkdotnet.org/. Библиотека доступна как opensource проект на github: https://github.com/dotnet/BenchmarkDotNet

Данная библиотека поддерживает широкий набор сред выполнения: .NET 5+, .NET Framework 4.6.1+, .NET Core 2.0+, Mono, NativeAOT

Библиотека доступна для всех основных ОС: Windows, Linux и macOS

Поддерживаемые архитектуры : x86, x64, ARM, ARM64, Wasm и LoongArch64

Рассмотрим базовое использование библиотеки. Для этого создадим новый проект консольного приложения. И добавим в него через пакетный менеджер Nuget пакет BenchmarkDotNet:

dotnet add package BenchmarkDotNet

Рассмотрим примитивную программу, которая склеивает строки в одну:

using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;
using System.Text;

BenchmarkRunner.Run<StringTest>();

public class StringTest
{
    string[] numbers = { "one", "two", "three", "four", "five", "six", "seven", "eight", "nine", "ten" };
    [Benchmark]
    public string WithStringBuilder()
    {
        StringBuilder stringBuilder = new StringBuilder();
        foreach (string s in numbers)
        {
            stringBuilder.Append(s);
            stringBuilder.Append(" ");
        }
        return stringBuilder.ToString();
    }
    [Benchmark]
    public string WithConcatenation()
    {
        string result = "";
        foreach (string s in numbers) result = result + s + " ";
        return result;
    }
    [Benchmark]
    public string WithInterpolation()
    {
        string result = "";
        foreach (string s in numbers) result = $"{result}{s} ";
        return result;
    }
}

Здесь определен класс StringTest. Он определяет массив numbers из 10 строк и три метода, которые используя разные техники, склеивают все эти строки в одну и возвращают ее в качестве результата. В частности, метод WithStringBuilder использует StringBuilder, метод WithConcatenation - обычное сложение строк, а метод WithInterpolation - интерполяцию строк.

Для тестирования все методы должны иметь модификатор public, и к этим методам должен применяться атрибут [Benchmark]. Кроме того, сам класс, который содержит эти методы, должен иметь модификатор public.

Для запуска теста вызывается метод BenchmarkRunner.Run, который типизируется тестируемым классом:

BenchmarkRunner.Run<StringTest>();

При запуске проекта стоит учитывать следующие особенности

  • Следует запускать проект в конфигурации "Release"

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

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

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

Итак, запустим проект с помощью следующей программы:

dotnet run -c Release

После завершения теста консоль отобразит результаты в виде наподобие следующего:

Тестирование приложения на C# с помощью BenchmarkDotNet

Результатом теста является следующая таблица

|            Method |     Mean |   Error |  StdDev |
|------------------ |---------:|--------:|--------:|
| WithStringBuilder | 157.7 ns | 3.23 ns | 3.17 ns |
| WithConcatenation | 166.7 ns | 1.59 ns | 1.49 ns |
| WithInterpolation | 171.0 ns | 3.28 ns | 3.37 ns |

Здесь столбец Mean представляет среднее время выполнения в наносекундах. Так, мы видим, что выполнение метода WithStringBuilder завершилось немногим быстрее, чем у других методов.

BenchmarkDotNet имеет очень много различных настроек и возможностей конфигурации. Рассмотрим одну из них. Например, нам надо узнать количество создаваемых объектов. Для этого мы можем использовать встроенный профиль MemoryDiagnoser. Он применяется в качестве атрибута к тестируемому классу:

[MemoryDiagnoser]
public class StringTest
{
//............

Если мы теперь запустим тест, то таблица результатов поплнится еще несколькими столбцами:

|            Method |     Mean |   Error |  StdDev |   Gen0 | Allocated |
|------------------ |---------:|--------:|--------:|-------:|----------:|
| WithStringBuilder | 153.8 ns | 0.37 ns | 0.33 ns | 0.0739 |     464 B |
| WithConcatenation | 167.8 ns | 0.73 ns | 0.57 ns | 0.1235 |     776 B |
| WithInterpolation | 165.7 ns | 0.40 ns | 0.35 ns | 0.1235 |     776 B |

Так, столбец Allocated указывает на размер управляемой памяти, выделяемой в процессе выполнения метода. Здесь мы видим, что при выполнении метода WithStringBuilder меньше всего выделялась память - 464 байта

Столбец Gen 0 сождержит количество чисток сборщика мусора на 1 000 операций. Например, если это значение равно 1, то это значит, что сборщик мусора GC вызывался один раз для 1000 вызовов метода для объектов поколения 0. Тут же опять мы видим, что метод WithStringBuilder несколько производительнее, чем остальные.

Помощь сайту
Юмани:
410011174743222
Перевод на карту
Номер карты:
4048415020898850