From f650bc57768f65fa1320453e675a50e8c174c0d2 Mon Sep 17 00:00:00 2001 From: gewuyou <95328647+GeWuYou@users.noreply.github.com> Date: Mon, 11 May 2026 12:49:28 +0800 Subject: [PATCH] =?UTF-8?q?test(cqrs-benchmarks):=20=E6=96=B0=E5=A2=9E=20n?= =?UTF-8?q?otification=20startup=20=E5=9F=BA=E5=87=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增 NotificationStartupBenchmarks,补齐 notification 的 Initialization 与 ColdStart 对称矩阵 - 复用最小宿主搭建路径,对齐 GFramework.CQRS、Mediator 与 MediatR 的单处理器首击发布对照 - 修复 cold-start 方法的资源释放时序,确保 benchmark 构建零警告通过 --- .../NotificationStartupBenchmarks.cs | 289 ++++++++++++++++++ 1 file changed, 289 insertions(+) create mode 100644 GFramework.Cqrs.Benchmarks/Messaging/NotificationStartupBenchmarks.cs diff --git a/GFramework.Cqrs.Benchmarks/Messaging/NotificationStartupBenchmarks.cs b/GFramework.Cqrs.Benchmarks/Messaging/NotificationStartupBenchmarks.cs new file mode 100644 index 00000000..5ede14f1 --- /dev/null +++ b/GFramework.Cqrs.Benchmarks/Messaging/NotificationStartupBenchmarks.cs @@ -0,0 +1,289 @@ +// Copyright (c) 2025-2026 GeWuYou +// SPDX-License-Identifier: Apache-2.0 + +using BenchmarkDotNet.Attributes; +using BenchmarkDotNet.Columns; +using BenchmarkDotNet.Configs; +using BenchmarkDotNet.Diagnosers; +using BenchmarkDotNet.Jobs; +using BenchmarkDotNet.Order; +using System; +using System.Threading; +using System.Threading.Tasks; +using GFramework.Core.Abstractions.Logging; +using GFramework.Core.Ioc; +using GFramework.Core.Logging; +using GFramework.Cqrs.Abstractions.Cqrs; +using MediatR; +using Microsoft.Extensions.DependencyInjection; +using ILogger = GFramework.Core.Abstractions.Logging.ILogger; +using GeneratedMediator = Mediator.Mediator; + +namespace GFramework.Cqrs.Benchmarks.Messaging; + +/// +/// 对比 notification 宿主在 GFramework.CQRS、NuGet `Mediator` 与 MediatR 之间的初始化与首次发布成本。 +/// +/// +/// 该矩阵刻意保持“单 notification + 单 handler + 最小宿主”的对称形状, +/// 只观察宿主构建与首个 publish 命中的额外开销,不把 fan-out 或自定义发布策略混入 startup 结论。 +/// +[Config(typeof(Config))] +public class NotificationStartupBenchmarks +{ + private static readonly ILogger RuntimeLogger = CreateLogger(nameof(NotificationStartupBenchmarks)); + private static readonly BenchmarkNotification Notification = new(Guid.NewGuid()); + + private MicrosoftDiContainer _container = null!; + private ICqrsRuntime _runtime = null!; + private ServiceProvider _serviceProvider = null!; + private IPublisher _publisher = null!; + private ServiceProvider _mediatorServiceProvider = null!; + private GeneratedMediator _mediator = null!; + + /// + /// 配置 notification startup benchmark 的公共输出格式。 + /// + private sealed class Config : ManualConfig + { + public Config() + { + AddJob(Job.Default + .WithId("ColdStart") + .WithInvocationCount(1) + .WithUnrollFactor(1)); + AddColumnProvider(DefaultColumnProviders.Instance); + AddColumn(new CustomColumn("Scenario", static (_, _) => "NotificationStartup"), TargetMethodColumn.Method, CategoriesColumn.Default); + AddDiagnoser(MemoryDiagnoser.Default); + AddLogicalGroupRules(BenchmarkLogicalGroupRule.ByCategory); + WithOrderer(new DefaultOrderer(SummaryOrderPolicy.FastestToSlowest, MethodOrderPolicy.Declared)); + } + } + + /// + /// 构建 startup benchmark 复用的最小 notification 宿主对象。 + /// + [GlobalSetup] + public void Setup() + { + Fixture.Setup("NotificationStartup", handlerCount: 1, pipelineCount: 0); + + _serviceProvider = CreateMediatRServiceProvider(); + _publisher = _serviceProvider.GetRequiredService(); + + _mediatorServiceProvider = CreateMediatorServiceProvider(); + _mediator = _mediatorServiceProvider.GetRequiredService(); + + _container = CreateGFrameworkContainer(); + _runtime = CreateGFrameworkRuntime(_container); + } + + /// + /// 在每次 cold-start 迭代前清空 dispatcher 静态缓存,确保每组 benchmark 都重新命中首次绑定路径。 + /// + [IterationSetup] + public void ResetColdStartCaches() + { + BenchmarkDispatcherCacheHelper.ClearDispatcherCaches(); + } + + /// + /// 释放 startup benchmark 复用的宿主对象。 + /// + [GlobalCleanup] + public void Cleanup() + { + BenchmarkCleanupHelper.DisposeAll(_container, _serviceProvider, _mediatorServiceProvider); + } + + /// + /// 返回已构建宿主中的 MediatR publisher,作为 initialization 组的句柄解析 baseline。 + /// + /// 当前 benchmark 复用的 MediatR publisher。 + [Benchmark(Baseline = true)] + [BenchmarkCategory("Initialization")] + public IPublisher Initialization_MediatR() + { + return _publisher; + } + + /// + /// 返回已构建宿主中的 GFramework.CQRS runtime,确保与 MediatR baseline 处于相同初始化阶段。 + /// + /// 当前 benchmark 复用的 GFramework.CQRS runtime。 + [Benchmark] + [BenchmarkCategory("Initialization")] + public ICqrsRuntime Initialization_GFrameworkCqrs() + { + return _runtime; + } + + /// + /// 返回已构建宿主中的 `Mediator` concrete mediator,作为 source-generated 对照组的初始化句柄。 + /// + /// 当前 benchmark 复用的 `Mediator` concrete mediator。 + [Benchmark] + [BenchmarkCategory("Initialization")] + public GeneratedMediator Initialization_Mediator() + { + return _mediator; + } + + /// + /// 在新宿主上首次发布 notification,作为 MediatR 的 cold-start baseline。 + /// + /// 代表首次 publish 完成的任务。 + [Benchmark(Baseline = true)] + [BenchmarkCategory("ColdStart")] + public async Task ColdStart_MediatR() + { + using var serviceProvider = CreateMediatRServiceProvider(); + var publisher = serviceProvider.GetRequiredService(); + await publisher.Publish(Notification, CancellationToken.None).ConfigureAwait(false); + } + + /// + /// 在新 runtime 上首次发布 notification,量化 GFramework.CQRS 的 first-hit 成本。 + /// + /// 代表首次 publish 完成的值任务。 + [Benchmark] + [BenchmarkCategory("ColdStart")] + public async ValueTask ColdStart_GFrameworkCqrs() + { + using var container = CreateGFrameworkContainer(); + var runtime = CreateGFrameworkRuntime(container); + await runtime.PublishAsync(BenchmarkContext.Instance, Notification, CancellationToken.None).ConfigureAwait(false); + } + + /// + /// 在新的 `Mediator` 宿主上首次发布 notification,量化 source-generated concrete path 的 cold-start 成本。 + /// + /// 代表首次 publish 完成的值任务。 + [Benchmark] + [BenchmarkCategory("ColdStart")] + public async ValueTask ColdStart_Mediator() + { + using var serviceProvider = CreateMediatorServiceProvider(); + var mediator = serviceProvider.GetRequiredService(); + await mediator.Publish(Notification, CancellationToken.None).ConfigureAwait(false); + } + + /// + /// 构建只承载当前 benchmark notification 的最小 GFramework.CQRS runtime。 + /// + /// + /// startup benchmark 只需要验证单 handler publish 的首击路径, + /// 因此这里继续使用单点手工注册,避免把更广泛的注册协调逻辑混入结果。 + /// + private static MicrosoftDiContainer CreateGFrameworkContainer() + { + return BenchmarkHostFactory.CreateFrozenGFrameworkContainer(static container => + { + container.RegisterTransient, BenchmarkNotificationHandler>(); + }); + } + + /// + /// 基于已冻结的 benchmark 容器构建最小 GFramework.CQRS runtime。 + /// + /// 当前 benchmark 拥有并负责释放的容器。 + /// 可直接发布 notification 的 runtime。 + private static ICqrsRuntime CreateGFrameworkRuntime(MicrosoftDiContainer container) + { + ArgumentNullException.ThrowIfNull(container); + return GFramework.Cqrs.CqrsRuntimeFactory.CreateRuntime(container, RuntimeLogger); + } + + /// + /// 构建只承载当前 benchmark notification handler 的最小 MediatR 对照宿主。 + /// + /// 可直接解析 的 DI 宿主。 + private static ServiceProvider CreateMediatRServiceProvider() + { + return BenchmarkHostFactory.CreateMediatRServiceProvider( + configure: null, + typeof(NotificationStartupBenchmarks), + static candidateType => candidateType == typeof(BenchmarkNotificationHandler), + ServiceLifetime.Transient); + } + + /// + /// 构建只承载当前 benchmark notification handler 的最小 `Mediator` 对照宿主。 + /// + /// 可直接解析 generated `Mediator.Mediator` 的 DI 宿主。 + private static ServiceProvider CreateMediatorServiceProvider() + { + return BenchmarkHostFactory.CreateMediatorServiceProvider(configure: null); + } + + /// + /// 为 benchmark 创建稳定的 fatal 级 logger,避免把日志成本混入 startup 测量。 + /// + /// logger 分类名。 + /// 当前 benchmark 使用的稳定 logger。 + private static ILogger CreateLogger(string categoryName) + { + LoggerFactoryResolver.Provider = new ConsoleLoggerFactoryProvider + { + MinLevel = LogLevel.Fatal + }; + return LoggerFactoryResolver.Provider.CreateLogger(categoryName); + } + + /// + /// Benchmark notification。 + /// + /// 通知标识。 + public sealed record BenchmarkNotification(Guid Id) : + GFramework.Cqrs.Abstractions.Cqrs.INotification, + Mediator.INotification, + MediatR.INotification; + + /// + /// 同时实现 GFramework.CQRS、NuGet `Mediator` 与 MediatR 契约的最小 notification handler。 + /// + public sealed class BenchmarkNotificationHandler : + GFramework.Cqrs.Abstractions.Cqrs.INotificationHandler, + Mediator.INotificationHandler, + MediatR.INotificationHandler + { + /// + /// 处理 GFramework.CQRS notification。 + /// + /// 当前 notification。 + /// 取消令牌。 + /// 表示处理完成的值任务。 + public ValueTask Handle(BenchmarkNotification notification, CancellationToken cancellationToken) + { + ArgumentNullException.ThrowIfNull(notification); + cancellationToken.ThrowIfCancellationRequested(); + return ValueTask.CompletedTask; + } + + /// + /// 处理 NuGet `Mediator` notification。 + /// + /// 当前 notification。 + /// 取消令牌。 + /// 表示处理完成的值任务。 + ValueTask Mediator.INotificationHandler.Handle( + BenchmarkNotification notification, + CancellationToken cancellationToken) + { + return Handle(notification, cancellationToken); + } + + /// + /// 处理 MediatR notification。 + /// + /// 当前 notification。 + /// 取消令牌。 + /// 表示处理完成的任务。 + Task MediatR.INotificationHandler.Handle( + BenchmarkNotification notification, + CancellationToken cancellationToken) + { + return Handle(notification, cancellationToken).AsTask(); + } + } +}