mirror of
https://github.com/GeWuYou/GFramework.git
synced 2026-05-07 00:39:00 +08:00
test(cqrs): 补充请求管道数量矩阵基准
- 新增 request pipeline 0/1/4 数量矩阵基准并保持 GFramework.Cqrs 与 MediatR 对照 - 更新 benchmark README 说明当前场景覆盖与后续扩展方向 - 补充 cqrs-rewrite 跟踪与 trace 的 RP-086 恢复点和验证记录
This commit is contained in:
parent
e6f98cb4af
commit
a8f98e467d
@ -0,0 +1,270 @@
|
||||
// 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;
|
||||
|
||||
namespace GFramework.Cqrs.Benchmarks.Messaging;
|
||||
|
||||
/// <summary>
|
||||
/// 对比不同 pipeline 行为数量下,单个 request 在直接调用、GFramework.CQRS runtime 与 MediatR 之间的 steady-state dispatch 开销。
|
||||
/// </summary>
|
||||
[Config(typeof(Config))]
|
||||
public class RequestPipelineBenchmarks
|
||||
{
|
||||
private MicrosoftDiContainer _container = null!;
|
||||
private ICqrsRuntime _runtime = null!;
|
||||
private ServiceProvider _serviceProvider = null!;
|
||||
private IMediator _mediatr = null!;
|
||||
private BenchmarkRequestHandler _baselineHandler = null!;
|
||||
private BenchmarkRequest _request = null!;
|
||||
|
||||
/// <summary>
|
||||
/// 控制当前场景注册的 pipeline 行为数量,保持与 `Mediator` benchmark 常见的“无行为 / 少量行为 / 多行为”矩阵一致。
|
||||
/// </summary>
|
||||
[Params(0, 1, 4)]
|
||||
public int PipelineCount { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 配置 request pipeline benchmark 的公共输出格式。
|
||||
/// </summary>
|
||||
private sealed class Config : ManualConfig
|
||||
{
|
||||
public Config()
|
||||
{
|
||||
AddJob(Job.Default);
|
||||
AddColumnProvider(DefaultColumnProviders.Instance);
|
||||
AddColumn(new CustomColumn("Scenario", static (_, _) => "RequestPipeline"));
|
||||
AddDiagnoser(MemoryDiagnoser.Default);
|
||||
WithOrderer(new DefaultOrderer(SummaryOrderPolicy.FastestToSlowest, MethodOrderPolicy.Declared));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 构建 request pipeline dispatch 所需的最小 runtime 宿主和对照对象。
|
||||
/// </summary>
|
||||
[GlobalSetup]
|
||||
public void Setup()
|
||||
{
|
||||
LoggerFactoryResolver.Provider = new ConsoleLoggerFactoryProvider
|
||||
{
|
||||
MinLevel = LogLevel.Fatal
|
||||
};
|
||||
Fixture.Setup("RequestPipeline", handlerCount: 1, pipelineCount: PipelineCount);
|
||||
|
||||
_container = new MicrosoftDiContainer();
|
||||
_baselineHandler = new BenchmarkRequestHandler();
|
||||
|
||||
_container.RegisterTransient<GFramework.Cqrs.Abstractions.Cqrs.IRequestHandler<BenchmarkRequest, BenchmarkResponse>, BenchmarkRequestHandler>();
|
||||
RegisterGFrameworkPipelineBehaviors(_container, PipelineCount);
|
||||
_runtime = GFramework.Cqrs.CqrsRuntimeFactory.CreateRuntime(
|
||||
_container,
|
||||
LoggerFactoryResolver.Provider.CreateLogger(nameof(RequestPipelineBenchmarks)));
|
||||
|
||||
var services = new ServiceCollection();
|
||||
services.AddSingleton<MediatR.IRequestHandler<BenchmarkRequest, BenchmarkResponse>, BenchmarkRequestHandler>();
|
||||
RegisterMediatRPipelineBehaviors(services, PipelineCount);
|
||||
services.AddMediatR(static options => options.RegisterServicesFromAssembly(typeof(RequestPipelineBenchmarks).Assembly));
|
||||
_serviceProvider = services.BuildServiceProvider();
|
||||
_mediatr = _serviceProvider.GetRequiredService<IMediator>();
|
||||
|
||||
_request = new BenchmarkRequest(Guid.NewGuid());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 释放 MediatR 对照组使用的 DI 宿主。
|
||||
/// </summary>
|
||||
[GlobalCleanup]
|
||||
public void Cleanup()
|
||||
{
|
||||
_serviceProvider.Dispose();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 直接调用 handler,作为 pipeline 编排之外的基线。
|
||||
/// </summary>
|
||||
[Benchmark(Baseline = true)]
|
||||
public ValueTask<BenchmarkResponse> SendRequest_Baseline()
|
||||
{
|
||||
return _baselineHandler.Handle(_request, CancellationToken.None);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 通过 GFramework.CQRS runtime 发送 request,并按当前矩阵配置执行 pipeline。
|
||||
/// </summary>
|
||||
[Benchmark]
|
||||
public ValueTask<BenchmarkResponse> SendRequest_GFrameworkCqrs()
|
||||
{
|
||||
return _runtime.SendAsync(BenchmarkContext.Instance, _request, CancellationToken.None);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 通过 MediatR 发送 request,并按当前矩阵配置执行 pipeline,作为外部设计对照。
|
||||
/// </summary>
|
||||
[Benchmark]
|
||||
public Task<BenchmarkResponse> SendRequest_MediatR()
|
||||
{
|
||||
return _mediatr.Send(_request, CancellationToken.None);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 按指定数量向 GFramework.CQRS 宿主注册最小 no-op pipeline 行为。
|
||||
/// </summary>
|
||||
/// <param name="container">当前 benchmark 使用的容器。</param>
|
||||
/// <param name="pipelineCount">要注册的行为数量。</param>
|
||||
/// <exception cref="ArgumentOutOfRangeException">行为数量不在支持的矩阵内时抛出。</exception>
|
||||
private static void RegisterGFrameworkPipelineBehaviors(MicrosoftDiContainer container, int pipelineCount)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(container);
|
||||
|
||||
switch (pipelineCount)
|
||||
{
|
||||
case 0:
|
||||
return;
|
||||
case 1:
|
||||
container.RegisterCqrsPipelineBehavior<BenchmarkPipelineBehavior1>();
|
||||
return;
|
||||
case 4:
|
||||
container.RegisterCqrsPipelineBehavior<BenchmarkPipelineBehavior1>();
|
||||
container.RegisterCqrsPipelineBehavior<BenchmarkPipelineBehavior2>();
|
||||
container.RegisterCqrsPipelineBehavior<BenchmarkPipelineBehavior3>();
|
||||
container.RegisterCqrsPipelineBehavior<BenchmarkPipelineBehavior4>();
|
||||
return;
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException(nameof(pipelineCount), pipelineCount,
|
||||
"Only the 0/1/4 pipeline matrix is supported.");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 按指定数量向 MediatR 宿主注册最小 no-op pipeline 行为。
|
||||
/// </summary>
|
||||
/// <param name="services">当前 benchmark 使用的服务集合。</param>
|
||||
/// <param name="pipelineCount">要注册的行为数量。</param>
|
||||
/// <exception cref="ArgumentOutOfRangeException">行为数量不在支持的矩阵内时抛出。</exception>
|
||||
private static void RegisterMediatRPipelineBehaviors(IServiceCollection services, int pipelineCount)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(services);
|
||||
|
||||
switch (pipelineCount)
|
||||
{
|
||||
case 0:
|
||||
return;
|
||||
case 1:
|
||||
services.AddSingleton<MediatR.IPipelineBehavior<BenchmarkRequest, BenchmarkResponse>, BenchmarkPipelineBehavior1>();
|
||||
return;
|
||||
case 4:
|
||||
services.AddSingleton<MediatR.IPipelineBehavior<BenchmarkRequest, BenchmarkResponse>, BenchmarkPipelineBehavior1>();
|
||||
services.AddSingleton<MediatR.IPipelineBehavior<BenchmarkRequest, BenchmarkResponse>, BenchmarkPipelineBehavior2>();
|
||||
services.AddSingleton<MediatR.IPipelineBehavior<BenchmarkRequest, BenchmarkResponse>, BenchmarkPipelineBehavior3>();
|
||||
services.AddSingleton<MediatR.IPipelineBehavior<BenchmarkRequest, BenchmarkResponse>, BenchmarkPipelineBehavior4>();
|
||||
return;
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException(nameof(pipelineCount), pipelineCount,
|
||||
"Only the 0/1/4 pipeline matrix is supported.");
|
||||
}
|
||||
}
|
||||
|
||||
/// <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));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 为 benchmark 提供统一的 no-op pipeline 行为实现,尽量把测量焦点保持在调度器与行为编排本身。
|
||||
/// </summary>
|
||||
public abstract class BenchmarkPipelineBehaviorBase :
|
||||
GFramework.Cqrs.Abstractions.Cqrs.IPipelineBehavior<BenchmarkRequest, BenchmarkResponse>,
|
||||
MediatR.IPipelineBehavior<BenchmarkRequest, BenchmarkResponse>
|
||||
{
|
||||
/// <summary>
|
||||
/// 透传 GFramework.CQRS pipeline,避免引入额外业务逻辑噪音。
|
||||
/// </summary>
|
||||
public ValueTask<BenchmarkResponse> Handle(
|
||||
BenchmarkRequest message,
|
||||
GFramework.Cqrs.Abstractions.Cqrs.MessageHandlerDelegate<BenchmarkRequest, BenchmarkResponse> next,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
return next(message, cancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 透传 MediatR pipeline,保持与 GFramework.CQRS 相同的 no-op 语义。
|
||||
/// </summary>
|
||||
Task<BenchmarkResponse> MediatR.IPipelineBehavior<BenchmarkRequest, BenchmarkResponse>.Handle(
|
||||
BenchmarkRequest request,
|
||||
RequestHandlerDelegate<BenchmarkResponse> next,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
return next();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// pipeline 行为槽位 1。
|
||||
/// </summary>
|
||||
public sealed class BenchmarkPipelineBehavior1 : BenchmarkPipelineBehaviorBase;
|
||||
|
||||
/// <summary>
|
||||
/// pipeline 行为槽位 2。
|
||||
/// </summary>
|
||||
public sealed class BenchmarkPipelineBehavior2 : BenchmarkPipelineBehaviorBase;
|
||||
|
||||
/// <summary>
|
||||
/// pipeline 行为槽位 3。
|
||||
/// </summary>
|
||||
public sealed class BenchmarkPipelineBehavior3 : BenchmarkPipelineBehaviorBase;
|
||||
|
||||
/// <summary>
|
||||
/// pipeline 行为槽位 4。
|
||||
/// </summary>
|
||||
public sealed class BenchmarkPipelineBehavior4 : BenchmarkPipelineBehaviorBase;
|
||||
}
|
||||
@ -16,6 +16,8 @@
|
||||
- 运行前输出并校验场景配置
|
||||
- `Messaging/RequestBenchmarks.cs`
|
||||
- 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/NotificationBenchmarks.cs`
|
||||
- `GFramework.Cqrs` runtime 与 `MediatR` 的单处理器 notification publish 对比
|
||||
- `Messaging/StreamingBenchmarks.cs`
|
||||
@ -31,6 +33,6 @@ dotnet run --project GFramework.Cqrs.Benchmarks/GFramework.Cqrs.Benchmarks.cspro
|
||||
|
||||
## 后续扩展方向
|
||||
|
||||
- pipeline behavior 数量矩阵
|
||||
- generated invoker provider 与纯反射 dispatch 对比
|
||||
- cold-start 与 registration 成本对比
|
||||
- request / stream 的 initialization 与首轮 dispatch 对比
|
||||
|
||||
@ -7,7 +7,7 @@ CQRS 迁移与收敛。
|
||||
|
||||
## 当前恢复点
|
||||
|
||||
- 恢复点编号:`CQRS-REWRITE-RP-085`
|
||||
- 恢复点编号:`CQRS-REWRITE-RP-086`
|
||||
- 当前阶段:`Phase 8`
|
||||
- 当前 PR 锚点:`PR #323`
|
||||
- 当前结论:
|
||||
@ -20,7 +20,8 @@ CQRS 迁移与收敛。
|
||||
- 当前 `RP-082` 已补齐基础 generation gate 的 request handler runtime contract 缺失分支
|
||||
- `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-085` 已补齐 stream request benchmark,对齐 `Mediator` messaging benchmark 的第二个核心场景
|
||||
- 当前 `RP-086` 已补齐 request pipeline `0 / 1 / 4` 数量矩阵,开始把 benchmark 关注点从单纯 messaging steady-state 扩展到行为编排开销
|
||||
- `ai-plan` active 入口现以 `PR #323` 和 `RP-082` 为唯一权威恢复锚点;`PR #307`、其他更早 PR 与阶段细节均以下方归档或说明为准
|
||||
|
||||
## 当前活跃事实
|
||||
@ -55,6 +56,9 @@ CQRS 迁移与收敛。
|
||||
- `dotnet build GFramework.Cqrs.Benchmarks/GFramework.Cqrs.Benchmarks.csproj -c Release`
|
||||
- 结果:通过,`0 warning / 0 error`
|
||||
- 备注:包含新增 `StreamingBenchmarks` 后再次复核通过
|
||||
- `dotnet build GFramework.Cqrs.Benchmarks/GFramework.Cqrs.Benchmarks.csproj -c Release`
|
||||
- 结果:通过,`0 warning / 0 error`
|
||||
- 备注:包含新增 `RequestPipelineBenchmarks` 后再次复核通过
|
||||
- `GIT_DIR=<worktree-git-dir> GIT_WORK_TREE=<worktree-root> python3 scripts/license-header.py --check`
|
||||
- 结果:通过
|
||||
- `git diff --check`
|
||||
@ -107,7 +111,7 @@ CQRS 迁移与收敛。
|
||||
## 下一推荐步骤
|
||||
|
||||
1. 继续处理 `PR #323` 的剩余 review 收尾,优先保持 `ai-plan` active 入口与 trace 的单一锚点一致
|
||||
2. 若继续推进“吸收 Mediator 设计哲学”的切片,优先扩展 benchmark 场景矩阵到 request pipeline 数量矩阵、cold-start / initialization 与 generated invoker provider 对照
|
||||
2. 若继续推进“吸收 Mediator 设计哲学”的切片,优先扩展 benchmark 场景矩阵到 cold-start / initialization 与 generated invoker provider 对照
|
||||
3. 在进入下一批 runtime / generator 收敛前,保持最小 Release build、targeted test 或 benchmark project build 作为权威验证
|
||||
|
||||
## 活跃文档
|
||||
|
||||
@ -334,3 +334,38 @@
|
||||
|
||||
1. 继续扩展 `GFramework.Cqrs.Benchmarks`,优先补齐 request pipeline 数量矩阵,随后再评估 cold-start / initialization
|
||||
2. 当需要验证 generated invoker provider 的实际收益时,把 request benchmark 扩展为 reflection / generated provider 对照,而不是只停留在框架间对比
|
||||
|
||||
### 阶段:request pipeline 数量矩阵(CQRS-REWRITE-RP-086)
|
||||
|
||||
- 继续沿用 `$gframework-batch-boot 50`,当前 branch diff 相对 `origin/main` 仍明显低于阈值
|
||||
- 本轮把 benchmark 关注点从单纯 messaging steady-state 扩展到 request pipeline 编排行为,原因是:
|
||||
- `ai-libs/Mediator` 的对照价值已经不只在 request / notification / stream 三个入口本身,还在 pipeline 包装策略与生命周期取舍
|
||||
- `GFramework.Cqrs.Internal.CqrsDispatcher` 已按 `behaviorCount` 缓存 `RequestPipelineExecutor<TResponse>` 形状,因此单独量化 `0 / 1 / 4` 个行为的 steady-state 开销有直接信息密度
|
||||
- 本轮新增:
|
||||
- `GFramework.Cqrs.Benchmarks/Messaging/RequestPipelineBenchmarks.cs`
|
||||
- `GFramework.Cqrs.Benchmarks/README.md` 中的 request pipeline 场景说明
|
||||
- 设计取舍:
|
||||
- 采用 `0 / 1 / 4` 个 pipeline 行为,而不是立即扩到更大的参数空间,先锁定最有代表性的无行为 / 少量行为 / 常见多行为矩阵
|
||||
- 使用最小 no-op 行为族,不引入日志、计时或上下文刷新逻辑,避免把测量结果污染成业务行为成本
|
||||
- `GFramework.Cqrs` 与 `MediatR` 侧都只注册当前 benchmark 请求对应的闭合行为类型,确保矩阵反映编排成本而非程序集扫描差异
|
||||
- 接受的只读 subagent 结论:
|
||||
- 下一批 benchmark 继续优先考虑 `cold-start / initialization` 与 `generated provider` 对照,而不是立即照搬 `Mediator` 的 large-project 维度
|
||||
- 当前 `GFramework.Cqrs.Benchmarks` 仍未接入 `Mediator` 包和 `GFramework.Cqrs.SourceGenerators`,因此本轮不扩成 `Mediator_IMediator` / generated-provider 对照,避免 scope 失控
|
||||
- 结论:
|
||||
- 当前 benchmark 项目已经覆盖 `Request`、`Notification`、`StreamRequest` 与 `RequestPipeline`
|
||||
- 后续若要继续贴近 `Mediator` 的 comparison benchmark,最值得优先补的是 initialization / first-hit 与 generated invoker provider,而不是继续横向堆更多 steady-state messaging 入口
|
||||
|
||||
### 验证(RP-086)
|
||||
|
||||
- `dotnet build GFramework.Cqrs.Benchmarks/GFramework.Cqrs.Benchmarks.csproj -c Release`
|
||||
- 结果:通过,`0 warning / 0 error`
|
||||
|
||||
### 当前 stop-condition 度量(RP-086)
|
||||
|
||||
- primary metric:branch diff files vs `origin/main`
|
||||
- 当前说明:提交前基于 `HEAD` 的 branch diff 仍为 `14` files,距离 `50` 文件阈值仍有明显余量
|
||||
|
||||
### 当前下一步(RP-086)
|
||||
|
||||
1. 提交本轮 request pipeline benchmark 后,继续扩展 `GFramework.Cqrs.Benchmarks`,优先补齐 initialization / cold-start 场景
|
||||
2. 当需要验证 dispatcher 预热与 source generator 收益时,引入 generated invoker provider 对照,并评估是否同时接入 `Mediator` concrete runtime 作为更贴近设计哲学的外部参照
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user