mirror of
https://github.com/GeWuYou/GFramework.git
synced 2026-05-06 16:16:44 +08:00
test(cqrs): 补充请求启动阶段基准
- 新增 request initialization 与 cold-start 基准并对齐当前 runtime 启动口径 - 通过清理 dispatcher 静态缓存隔离 GFramework.Cqrs 首次分发测量结果 - 更新 benchmark README 与 cqrs-rewrite RP-087 跟踪记录
This commit is contained in:
parent
a8f98e467d
commit
e0bbf13d88
217
GFramework.Cqrs.Benchmarks/Messaging/RequestStartupBenchmarks.cs
Normal file
217
GFramework.Cqrs.Benchmarks/Messaging/RequestStartupBenchmarks.cs
Normal file
@ -0,0 +1,217 @@
|
||||
// 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.Reflection;
|
||||
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;
|
||||
|
||||
namespace GFramework.Cqrs.Benchmarks.Messaging;
|
||||
|
||||
/// <summary>
|
||||
/// 对比 request 宿主的初始化与首次分发成本,作为后续吸收 `Mediator` comparison benchmark 设计的 startup 基线。
|
||||
/// </summary>
|
||||
[Config(typeof(Config))]
|
||||
public class RequestStartupBenchmarks
|
||||
{
|
||||
private static readonly ILogger RuntimeLogger = CreateLogger(nameof(RequestStartupBenchmarks));
|
||||
private static readonly BenchmarkRequest Request = new(Guid.NewGuid());
|
||||
|
||||
private ServiceProvider _serviceProvider = null!;
|
||||
private IMediator _mediatr = null!;
|
||||
|
||||
/// <summary>
|
||||
/// 配置 request startup benchmark 的公共输出格式。
|
||||
/// </summary>
|
||||
private sealed class Config : ManualConfig
|
||||
{
|
||||
public Config()
|
||||
{
|
||||
AddJob(Job.Default);
|
||||
AddColumnProvider(DefaultColumnProviders.Instance);
|
||||
AddColumn(new CustomColumn("Scenario", static (_, _) => "RequestStartup"));
|
||||
AddDiagnoser(MemoryDiagnoser.Default);
|
||||
WithOrderer(new DefaultOrderer(SummaryOrderPolicy.FastestToSlowest, MethodOrderPolicy.Declared));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 构建 steady-state 初始化 benchmark 复用的宿主对象。
|
||||
/// </summary>
|
||||
[GlobalSetup]
|
||||
public void Setup()
|
||||
{
|
||||
Fixture.Setup("RequestStartup", handlerCount: 1, pipelineCount: 0);
|
||||
|
||||
_serviceProvider = CreateMediatRServiceProvider();
|
||||
_mediatr = _serviceProvider.GetRequiredService<IMediator>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 释放 MediatR 对照组使用的 DI 宿主。
|
||||
/// </summary>
|
||||
[GlobalCleanup]
|
||||
public void Cleanup()
|
||||
{
|
||||
_serviceProvider.Dispose();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 解析 MediatR mediator,作为 startup 句柄解析成本的 baseline。
|
||||
/// </summary>
|
||||
[Benchmark(Baseline = true)]
|
||||
[BenchmarkCategory("Initialization")]
|
||||
public IMediator Initialization_MediatR()
|
||||
{
|
||||
return _serviceProvider.GetRequiredService<IMediator>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 创建 GFramework.CQRS runtime,作为同层级 startup 句柄创建成本的对照。
|
||||
/// </summary>
|
||||
[Benchmark]
|
||||
[BenchmarkCategory("Initialization")]
|
||||
public ICqrsRuntime Initialization_GFrameworkCqrs()
|
||||
{
|
||||
return CreateGFrameworkRuntime();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 在新宿主上首次发送 request,作为 MediatR 的 cold-start baseline。
|
||||
/// </summary>
|
||||
[Benchmark(Baseline = true)]
|
||||
[BenchmarkCategory("ColdStart")]
|
||||
public async Task<BenchmarkResponse> ColdStart_MediatR()
|
||||
{
|
||||
using var serviceProvider = CreateMediatRServiceProvider();
|
||||
var mediator = serviceProvider.GetRequiredService<IMediator>();
|
||||
return await mediator.Send(Request, CancellationToken.None).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 在清空 dispatcher 静态缓存后,于新宿主上首次发送 request,量化 GFramework.CQRS 的 first-hit 成本。
|
||||
/// </summary>
|
||||
[Benchmark]
|
||||
[BenchmarkCategory("ColdStart")]
|
||||
public ValueTask<BenchmarkResponse> ColdStart_GFrameworkCqrs()
|
||||
{
|
||||
ClearDispatcherCaches();
|
||||
var runtime = CreateGFrameworkRuntime();
|
||||
return runtime.SendAsync(BenchmarkContext.Instance, Request, CancellationToken.None);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 构建只承载当前 benchmark request 的最小 GFramework.CQRS runtime。
|
||||
/// </summary>
|
||||
private static ICqrsRuntime CreateGFrameworkRuntime()
|
||||
{
|
||||
var container = new MicrosoftDiContainer();
|
||||
container.RegisterTransient<GFramework.Cqrs.Abstractions.Cqrs.IRequestHandler<BenchmarkRequest, BenchmarkResponse>, BenchmarkRequestHandler>();
|
||||
return GFramework.Cqrs.CqrsRuntimeFactory.CreateRuntime(container, RuntimeLogger);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 构建只承载当前 benchmark request 的最小 MediatR 对照宿主。
|
||||
/// </summary>
|
||||
private static ServiceProvider CreateMediatRServiceProvider()
|
||||
{
|
||||
var services = new ServiceCollection();
|
||||
services.AddSingleton<MediatR.IRequestHandler<BenchmarkRequest, BenchmarkResponse>, BenchmarkRequestHandler>();
|
||||
services.AddMediatR(static options => options.RegisterServicesFromAssembly(typeof(RequestStartupBenchmarks).Assembly));
|
||||
return services.BuildServiceProvider();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 为 benchmark 创建稳定的 fatal 级 logger,避免把日志成本混入 startup 测量。
|
||||
/// </summary>
|
||||
private static ILogger CreateLogger(string categoryName)
|
||||
{
|
||||
LoggerFactoryResolver.Provider = new ConsoleLoggerFactoryProvider
|
||||
{
|
||||
MinLevel = LogLevel.Fatal
|
||||
};
|
||||
return LoggerFactoryResolver.Provider.CreateLogger(categoryName);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 清空 dispatcher 静态缓存,避免同一进程中的前一轮分发污染 cold-start 结果。
|
||||
/// </summary>
|
||||
private static void ClearDispatcherCaches()
|
||||
{
|
||||
ClearDispatcherCache("NotificationDispatchBindings");
|
||||
ClearDispatcherCache("RequestDispatchBindings");
|
||||
ClearDispatcherCache("StreamDispatchBindings");
|
||||
ClearDispatcherCache("GeneratedRequestInvokers");
|
||||
ClearDispatcherCache("GeneratedStreamInvokers");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 通过反射定位并清空 dispatcher 的指定缓存字段。
|
||||
/// </summary>
|
||||
/// <param name="fieldName">要清理的静态缓存字段名。</param>
|
||||
private static void ClearDispatcherCache(string fieldName)
|
||||
{
|
||||
var field = typeof(GFramework.Cqrs.CqrsRuntimeFactory).Assembly
|
||||
.GetType("GFramework.Cqrs.Internal.CqrsDispatcher", throwOnError: true)!
|
||||
.GetField(fieldName, BindingFlags.NonPublic | BindingFlags.Static)
|
||||
?? throw new InvalidOperationException($"Missing dispatcher cache field {fieldName}.");
|
||||
var cache = field.GetValue(null)
|
||||
?? throw new InvalidOperationException($"Dispatcher cache field {fieldName} returned null.");
|
||||
var clearMethod = cache.GetType().GetMethod("Clear", BindingFlags.Public | BindingFlags.Instance)
|
||||
?? throw new InvalidOperationException(
|
||||
$"Dispatcher cache field {fieldName} does not expose a Clear method.");
|
||||
_ = clearMethod.Invoke(cache, null);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Benchmark request。
|
||||
/// </summary>
|
||||
/// <param name="Id">请求标识。</param>
|
||||
public sealed record BenchmarkRequest(Guid Id) :
|
||||
GFramework.Cqrs.Abstractions.Cqrs.IRequest<BenchmarkResponse>,
|
||||
MediatR.IRequest<BenchmarkResponse>;
|
||||
|
||||
/// <summary>
|
||||
/// Benchmark response。
|
||||
/// </summary>
|
||||
/// <param name="Id">响应标识。</param>
|
||||
public sealed record BenchmarkResponse(Guid Id);
|
||||
|
||||
/// <summary>
|
||||
/// 同时实现 GFramework.CQRS 与 MediatR 契约的最小 request handler。
|
||||
/// </summary>
|
||||
public sealed class BenchmarkRequestHandler :
|
||||
GFramework.Cqrs.Abstractions.Cqrs.IRequestHandler<BenchmarkRequest, BenchmarkResponse>,
|
||||
MediatR.IRequestHandler<BenchmarkRequest, BenchmarkResponse>
|
||||
{
|
||||
/// <summary>
|
||||
/// 处理 GFramework.CQRS request。
|
||||
/// </summary>
|
||||
public ValueTask<BenchmarkResponse> Handle(BenchmarkRequest request, CancellationToken cancellationToken)
|
||||
{
|
||||
return ValueTask.FromResult(new BenchmarkResponse(request.Id));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 处理 MediatR request。
|
||||
/// </summary>
|
||||
Task<BenchmarkResponse> MediatR.IRequestHandler<BenchmarkRequest, BenchmarkResponse>.Handle(
|
||||
BenchmarkRequest request,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
return Task.FromResult(new BenchmarkResponse(request.Id));
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -18,6 +18,8 @@
|
||||
- direct handler、`GFramework.Cqrs` runtime 与 `MediatR` 的 request steady-state dispatch 对比
|
||||
- `Messaging/RequestPipelineBenchmarks.cs`
|
||||
- `0 / 1 / 4` 个 pipeline 行为下,direct handler、`GFramework.Cqrs` runtime 与 `MediatR` 的 request steady-state dispatch 对比
|
||||
- `Messaging/RequestStartupBenchmarks.cs`
|
||||
- `Initialization` 与 `ColdStart` 两组 request startup 成本对比,补齐与 `Mediator` comparison benchmark 更接近的 startup 维度
|
||||
- `Messaging/NotificationBenchmarks.cs`
|
||||
- `GFramework.Cqrs` runtime 与 `MediatR` 的单处理器 notification publish 对比
|
||||
- `Messaging/StreamingBenchmarks.cs`
|
||||
@ -34,5 +36,5 @@ dotnet run --project GFramework.Cqrs.Benchmarks/GFramework.Cqrs.Benchmarks.cspro
|
||||
## 后续扩展方向
|
||||
|
||||
- generated invoker provider 与纯反射 dispatch 对比
|
||||
- cold-start 与 registration 成本对比
|
||||
- request / stream 的 initialization 与首轮 dispatch 对比
|
||||
- registration / service lifetime 矩阵
|
||||
- request / stream 的 generated provider 与 concrete runtime 对照
|
||||
|
||||
@ -7,7 +7,7 @@ CQRS 迁移与收敛。
|
||||
|
||||
## 当前恢复点
|
||||
|
||||
- 恢复点编号:`CQRS-REWRITE-RP-086`
|
||||
- 恢复点编号:`CQRS-REWRITE-RP-087`
|
||||
- 当前阶段:`Phase 8`
|
||||
- 当前 PR 锚点:`PR #323`
|
||||
- 当前结论:
|
||||
@ -21,7 +21,8 @@ CQRS 迁移与收敛。
|
||||
- `RP-083` 已补齐 mixed direct / reflected-implementation request 与 stream invoker provider 发射顺序回归
|
||||
- `RP-084` 已引入独立 `GFramework.Cqrs.Benchmarks` 项目,作为持续吸收 `Mediator` benchmark 组织方式的第一落点
|
||||
- `RP-085` 已补齐 stream request benchmark,对齐 `Mediator` messaging benchmark 的第二个核心场景
|
||||
- 当前 `RP-086` 已补齐 request pipeline `0 / 1 / 4` 数量矩阵,开始把 benchmark 关注点从单纯 messaging steady-state 扩展到行为编排开销
|
||||
- `RP-086` 已补齐 request pipeline `0 / 1 / 4` 数量矩阵,开始把 benchmark 关注点从单纯 messaging steady-state 扩展到行为编排开销
|
||||
- 当前 `RP-087` 已补齐 request startup benchmark,把 initialization 与 cold-start 维度正式纳入 `GFramework.Cqrs.Benchmarks`
|
||||
- `ai-plan` active 入口现以 `PR #323` 和 `RP-082` 为唯一权威恢复锚点;`PR #307`、其他更早 PR 与阶段细节均以下方归档或说明为准
|
||||
|
||||
## 当前活跃事实
|
||||
@ -59,6 +60,9 @@ CQRS 迁移与收敛。
|
||||
- `dotnet build GFramework.Cqrs.Benchmarks/GFramework.Cqrs.Benchmarks.csproj -c Release`
|
||||
- 结果:通过,`0 warning / 0 error`
|
||||
- 备注:包含新增 `RequestPipelineBenchmarks` 后再次复核通过
|
||||
- `dotnet build GFramework.Cqrs.Benchmarks/GFramework.Cqrs.Benchmarks.csproj -c Release`
|
||||
- 结果:通过,`0 warning / 0 error`
|
||||
- 备注:包含新增 `RequestStartupBenchmarks` 后再次复核通过
|
||||
- `GIT_DIR=<worktree-git-dir> GIT_WORK_TREE=<worktree-root> python3 scripts/license-header.py --check`
|
||||
- 结果:通过
|
||||
- `git diff --check`
|
||||
@ -111,7 +115,7 @@ CQRS 迁移与收敛。
|
||||
## 下一推荐步骤
|
||||
|
||||
1. 继续处理 `PR #323` 的剩余 review 收尾,优先保持 `ai-plan` active 入口与 trace 的单一锚点一致
|
||||
2. 若继续推进“吸收 Mediator 设计哲学”的切片,优先扩展 benchmark 场景矩阵到 cold-start / initialization 与 generated invoker provider 对照
|
||||
2. 若继续推进“吸收 Mediator 设计哲学”的切片,优先扩展 benchmark 场景矩阵到 generated invoker provider、registration / service lifetime 与更贴近 `Mediator` concrete runtime 的对照
|
||||
3. 在进入下一批 runtime / generator 收敛前,保持最小 Release build、targeted test 或 benchmark project build 作为权威验证
|
||||
|
||||
## 活跃文档
|
||||
|
||||
@ -369,3 +369,33 @@
|
||||
|
||||
1. 提交本轮 request pipeline benchmark 后,继续扩展 `GFramework.Cqrs.Benchmarks`,优先补齐 initialization / cold-start 场景
|
||||
2. 当需要验证 dispatcher 预热与 source generator 收益时,引入 generated invoker provider 对照,并评估是否同时接入 `Mediator` concrete runtime 作为更贴近设计哲学的外部参照
|
||||
|
||||
### 阶段:request startup 基线(CQRS-REWRITE-RP-087)
|
||||
|
||||
- 继续沿用 `$gframework-batch-boot 50`,当前 branch diff 相对 `origin/main` 仍明显低于阈值
|
||||
- 本轮目标:把 benchmark 从 steady-state dispatch 再向前推进一层,补齐与 `ai-libs/Mediator/benchmarks/Mediator.Benchmarks/Messaging/Comparison/*` 更接近的 startup 维度
|
||||
- 本轮新增:
|
||||
- `GFramework.Cqrs.Benchmarks/Messaging/RequestStartupBenchmarks.cs`
|
||||
- `GFramework.Cqrs.Benchmarks/README.md` 中的 startup 场景说明
|
||||
- 设计取舍:
|
||||
- `Initialization` 只测“从已配置宿主解析/创建 runtime 句柄”的成本,不把完整架构初始化混入 benchmark
|
||||
- `ColdStart` 只测新宿主上的首次 request send;`GFramework.Cqrs` 侧在每次 benchmark 前通过反射清空 dispatcher 静态缓存,避免把热缓存误当 first-hit
|
||||
- `ColdStart_MediatR` 改为真正 `await` 完任务后再释放 `ServiceProvider`,以满足 `Meziantou.Analyzer` 对资源生命周期的要求,并避免 benchmark 本身含有错误宿主释放语义
|
||||
- 结论:
|
||||
- 当前 benchmark 项目已经覆盖 `Request`、`Notification`、`StreamRequest`、`RequestPipeline`、`RequestStartup`
|
||||
- 后续若继续贴近 `Mediator` comparison benchmark,下一批最有价值的是 generated invoker provider、registration / service lifetime 与 concrete runtime 外部对照,而不是继续只加同层 steady-state case
|
||||
|
||||
### 验证(RP-087)
|
||||
|
||||
- `dotnet build GFramework.Cqrs.Benchmarks/GFramework.Cqrs.Benchmarks.csproj -c Release`
|
||||
- 结果:通过,`0 warning / 0 error`
|
||||
|
||||
### 当前 stop-condition 度量(RP-087)
|
||||
|
||||
- primary metric:branch diff files vs `origin/main`
|
||||
- 当前说明:提交前 branch diff 仍远低于 `50` 文件阈值,可继续下一批 benchmark 或低风险 runtime 对照切片
|
||||
|
||||
### 当前下一步(RP-087)
|
||||
|
||||
1. 提交本轮 request startup benchmark 后,继续扩展 `GFramework.Cqrs.Benchmarks`,优先评估 generated invoker provider 与 registration / service lifetime 矩阵
|
||||
2. 若要更贴近 `Mediator` 的 comparison benchmark 设计哲学,评估是否在 benchmark 项目中同时接入 `Mediator` concrete runtime 对照,而不只保留 `MediatR`
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user