SwitchMediator v3: медиатор без аллокаций, который остаётся дружелюбным к AOT
SwitchMediator v3 нацелен на бесаллокационный, AOT-дружественный диспатч для CQRS-сервисов на .NET 9 и .NET 10. Что это значит и как замерить собственный медиатор.
Если вы когда-нибудь профилировали “чистую” CQRS-кодовую базу и обнаруживали смерть от тысячи аллокаций в слое медиатора, сегодняшний релиз SwitchMediator v3 стоит посмотреть. Автор прямо заявляет о бесаллокационном и AOT-дружелюбном поведении — именно та комбинация, которую хочется получить в сервисах .NET 9 и .NET 10, заботящихся о задержках.
Где типичные реализации медиатора подтекают аллокациями
Есть несколько распространённых паттернов, которые тихо аллоцируют:
- Boxing и интерфейсный диспатч: особенно когда обработчики хранятся как
objectи приводятся к типу на каждом запросе. - Списки pipeline behaviors: аллоцируют энумераторы, замыкания и промежуточные списки.
- Поиск обработчиков через рефлексию: удобно, но плохо сочетается с trimming и native AOT.
AOT-дружелюбный медиатор обычно делает наоборот: явно регистрирует обработчики и строит логику диспатча на известных дженерик-типах, а не на рантайм-рефлексии.
Маленький “до и после” каркас бенчмарка
Даже если вы не возьмёте SwitchMediator, вам стоит замерить границу своего медиатора. Это минимальный каркас, который можно бросить в консольное приложение под .NET 10, чтобы понять свою отправную точку.
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;
public static class Program
{
public static void Main() => BenchmarkRunner.Run<MediatorBench>();
}
public sealed record Ping(int Value);
public sealed record Pong(int Value);
public interface IMediator
{
ValueTask<Pong> Send(Ping request, CancellationToken ct = default);
}
public sealed class MediatorBench
{
private readonly IMediator _mediator = /* wire your mediator here */;
[Benchmark]
public async ValueTask<Pong> SendPing() => await _mediator.Send(new Ping(123));
}
На что я смотрю:
- Аллоцированные байты на операцию должны быть близки к нулю для тривиальных запросов.
- Пропускная способность должна масштабироваться с работой обработчика, а не с накладными расходами диспатча.
Если вы видите аллокации на пути диспатча, обычно их находят, переключив тип возврата на ValueTask (как выше) и сохраняя типы запроса/ответа как записи или структуры, предсказуемые для JIT.
AOT-дружелюбный обычно значит “явный”
Если вы экспериментируете с native AOT на .NET 10, перегруженные рефлексией медиаторы — одно из первого, что ломается.
Архитектурный компромисс простой:
- Сканирование рефлексией: отличный опыт разработчика, слабая история с trimming/AOT.
- Явная регистрация: чуть больше настройки, но предсказуемо и дружелюбно к trimming.
Подача SwitchMediator говорит, что он склоняется к явному концу спектра. Это совпадает с моим подходом к работе над производительностью: я готов написать несколько лишних строк проводки, если они дают предсказуемое поведение в проде.
Если хотите подробностей, начните с треда анонса и перейдите оттуда по ссылке на репозиторий: https://www.reddit.com/r/dotnet/comments/1q6yl0n/switchmediator_v3_is_out_now_a_zeroalloc/
Comments
Sign in with GitHub to comment. Reactions and replies thread back to the comments repo.