Если вы когда-нибудь задумывались, можно ли запустить бенчмарк, используя всего один C#-файл, то ответ: да, можно. Начиная с .NET 10, существует возможность создавать C#-приложения в одном *.cs‑файле. Проблема в том, что BenchmarkDotNet (BDN) не поддерживает такие бенчмарки с настройками по умолчанию. В этой статье я покажу, как обойти это ограничение, используя режим in-process.

Что такое однофайловые C#-приложения?

Однофайловые C#-приложения — это функционал, который появился в .NET 10 и позволяет поместить весь код в один *.cs-файл и запускать его напрямую:

// HelloWorld.cs
Console.WriteLine("Hello, world!");

Запуск:

dotnet HelloWorld.cs
# или
dotnet run HelloWorld.cs

П��чему BenchmarkDotNet не работает «из коробки»?

По умолчанию BDN использует изоляцию на уровне процесса (process-level isolation): он генерирует, собирает и запускает отдельное консольное приложение для каждого бенчмарка. Такая изоляция обеспечивает более точные и стабильные измерения. В однофайловых приложениях .NET генерирует проект и собирает артефакты во временную папку. У BDN пока нет функционала для работы с такими проектами.

Обходим ограничение, используя in-process режим

Чтобы запустить бенчмарк в том же процессе, нужно использовать атрибут InProcess. Учтите, что такие измерения менее изолированы и на результаты может влиять хост-процесс.

Минимальный рабочий пример:

#:package BenchmarkDotNet@0.15.8
#:property Optimize=true
#:property Configuration=Release
#:property PublishAot=false

using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;

BenchmarkRunner.Run<Benchmarks>();

[InProcess]
public class Benchmarks
{
    [Benchmark]
    public Task Example() => Task.Delay(100);
}

Возможно вы заметили, что указаны флаги Optimize=true, Configuration=Release и PublishAot=false.

Флаги Optimize и Configuration нужны потому, что иначе .NET запустит бенчмарк в режиме Debug и без оптимизаций. А, как известно, бенчмарки в режиме Debug менее точные.

Флаг PublishAot отключает AOT, который для файловых приложений включён по умолчанию. AOT может быть полезен для уменьшения времени запуска вашего однофайлового приложения, и BDN поддерживает NativeAOT, но для этого требуется дополнительная настройка BDN.

Запуск бенчмарка

Чтобы запустить бенчмарк, выполните:

dotnet run Benchmark.cs

Если у вас Linux или macOS, то можно использовать shebang. Сначала найдите путь до dotnet:

which dotnet

Затем добавьте shebang в начало Benchmark.cs. В моём случае путь был /usr/bin/dotnet:

#!/usr/bin/dotnet run
#:package BenchmarkDotNet@0.15.8
// ... остальной код

Сделайте файл исполняемым:

chmod +x Benchmark.cs

И запустите:

./Benchmark.cs

Вне зависимости от способа запуска вы увидите примерно такие результаты:

| Method  |     Mean |   Error |  StdDev |
| ------- | -------: | ------: | ------: |
| Example | 100.4 ms | 0.31 ms | 0.26 ms |

Вывод

Однофайловые C#-приложения можно использовать для бенчмаркинга, но для этого нужно запускать их в режиме in-process. Используя такой подход, помните, что такие бенчмарки менее изолированы и на результаты может влиять хост-процесс.