mirror of
https://github.com/GeWuYou/GFramework.git
synced 2026-05-08 17:44:29 +08:00
perf(cqrs): 收紧性能回归门槛并忽略基准产物
- 更新 BenchmarkDotNet 生成目录忽略规则,避免本地基准产物污染工作树 - 补充 CQRS benchmark 回归要求与性能目标,要求相关改动后复跑 request 基准 - 更新 cqrs-rewrite 跟踪文档并记录最新 request 基准结果
This commit is contained in:
parent
18018966f9
commit
5da4a5893b
1
.gitignore
vendored
1
.gitignore
vendored
@ -26,3 +26,4 @@ ai-libs/
|
||||
.codex
|
||||
# tool
|
||||
.venv/
|
||||
BenchmarkDotNet.Artifacts/
|
||||
|
||||
@ -15,7 +15,7 @@
|
||||
- `Messaging/Fixture.cs`
|
||||
- 运行前输出并校验场景配置
|
||||
- `Messaging/RequestBenchmarks.cs`
|
||||
- direct handler、`GFramework.Cqrs` runtime、`ai-libs/Mediator` source-generated concrete path 与 `MediatR` 的 request steady-state dispatch 对比
|
||||
- direct handler、NuGet `Mediator` source-generated concrete path、`GFramework.Cqrs` runtime 与 `MediatR` 的 request steady-state dispatch 对比
|
||||
- `Messaging/RequestLifetimeBenchmarks.cs`
|
||||
- `Singleton / Transient` 两类 handler 生命周期下,direct handler、`GFramework.Cqrs` runtime 与 `MediatR` 的 request steady-state dispatch 对比
|
||||
- `Messaging/RequestPipelineBenchmarks.cs`
|
||||
@ -39,10 +39,18 @@ dotnet run --project GFramework.Cqrs.Benchmarks/GFramework.Cqrs.Benchmarks.cspro
|
||||
|
||||
也可以通过 `BenchmarkDotNet` 过滤器只运行某一类场景。
|
||||
|
||||
## 当前约束
|
||||
|
||||
- `BenchmarkDotNet.Artifacts/` 属于本地生成输出,默认加入仓库忽略,不作为常规提交内容
|
||||
- 只要变更影响 `GFramework.Cqrs` request dispatch、DI 解析热路径、invoker/provider、pipeline 或 benchmark 宿主,就必须至少复跑:
|
||||
- `RequestBenchmarks.SendRequest_*`
|
||||
- `RequestLifetimeBenchmarks.SendRequest_*`
|
||||
- 当前性能目标不是超过 source-generated `Mediator`,而是让默认 request steady-state 路径尽量接近它,并至少稳定快于基于反射 / 扫描的 `MediatR`
|
||||
|
||||
## 后续扩展方向
|
||||
|
||||
- request / stream 的真实 source-generator 产物与 handwritten generated provider 对照
|
||||
- `ai-libs/Mediator` 的 transient / scoped compile-time lifetime 矩阵对照
|
||||
- `Mediator` 的 transient / scoped compile-time lifetime 矩阵对照
|
||||
- stream handler 生命周期矩阵
|
||||
- 带真实显式作用域边界的 scoped host 对照
|
||||
- generated invoker provider 与纯反射 dispatch / 建流对比继续扩展到更多场景
|
||||
|
||||
@ -7,7 +7,7 @@ CQRS 迁移与收敛。
|
||||
|
||||
## 当前恢复点
|
||||
|
||||
- 恢复点编号:`CQRS-REWRITE-RP-101`
|
||||
- 恢复点编号:`CQRS-REWRITE-RP-102`
|
||||
- 当前阶段:`Phase 8`
|
||||
- 当前 PR 锚点:`PR #339`
|
||||
- 当前结论:
|
||||
@ -38,20 +38,23 @@ CQRS 迁移与收敛。
|
||||
- 当前 `RP-100` 已使用 `$gframework-pr-review` 复核 `PR #339` latest-head review:收口 `RegisterCqrsStreamPipelineBehavior<TBehavior>()` 的异常契约文档、为 `StreamPipelineInvocation.GetContinuation(...)` 补齐并发 continuation 缓存说明、抽取 `MicrosoftDiContainer` 的 CQRS 行为注册公共逻辑,并顺手修复当前 branch diff 内 `ICqrsRequestInvokerProvider.cs` 的 XML 缩进格式问题
|
||||
- 当前 `RP-101` 已按用户新增 benchmark 诉求收口 request 热路径:为 `IIocContainer` 新增不激活实例的 `HasRegistration(Type)`、让 dispatcher 在 `0 pipeline` 场景下跳过空行为解析,并为 `MicrosoftDiContainer` 的热路径查询补齐 debug-level 守卫,避免无效日志字符串分配
|
||||
- 当前 `RP-101` 已把 `GFramework.Cqrs.Benchmarks` 的 `Mediator` 对照组收口为官方 NuGet 引用(`Mediator.Abstractions` / `Mediator.SourceGenerator` `3.0.2`),不再使用本地 `ai-libs/Mediator` project reference;`RequestBenchmarks` 现已新增 source-generated concrete `Mediator` 对照方法,并通过 `RequestLifetimeBenchmarks` 复核 hot path 收口后的新基线
|
||||
- `ai-plan` active 入口现以 `RP-101` 为最新恢复锚点;`PR #339`、`PR #334`、`PR #331`、`PR #326`、`PR #323`、`PR #307` 与其他更早阶段细节均以下方归档或说明为准
|
||||
- 当前 `RP-102` 已将 `BenchmarkDotNet.Artifacts/` 收口为默认忽略路径,并把 request steady-state / lifetime benchmark 复跑升级为 CQRS 性能相关改动的默认回归门槛;当前阶段目标明确为“持续逼近 source-generated `Mediator`,并至少稳定超过反射版 `MediatR`”
|
||||
- `ai-plan` active 入口现以 `RP-102` 为最新恢复锚点;`PR #339`、`PR #334`、`PR #331`、`PR #326`、`PR #323`、`PR #307` 与其他更早阶段细节均以下方归档或说明为准
|
||||
|
||||
## 当前活跃事实
|
||||
|
||||
- 当前分支为 `feat/cqrs-optimization`
|
||||
- 本轮 `$gframework-batch-boot 50` 以 `origin/main` (`5dc2dd25`, 2026-05-08 09:08:37 +0800) 为基线;本地 `main` (`c2d22285`) 已落后,不作为 branch diff 基线
|
||||
- 当前分支相对 `origin/main` 的累计 branch diff 仍为 `0 files / 0 lines`;本轮待提交工作树包含 9 个跟踪文件修改,另有 `BenchmarkDotNet.Artifacts/` 生成输出未纳入提交,仍明显低于 `$gframework-batch-boot 50` 的文件阈值
|
||||
- 当前分支相对 `origin/main` 的累计 branch diff 仍为 `10 files / 298 lines`;本轮待提交工作树以 `.gitignore`、benchmark README 与 active tracking/trace 更新为主,仍明显低于 `$gframework-batch-boot 50` 的文件阈值
|
||||
- `GFramework.Cqrs.Benchmarks` 作为 benchmark 基础设施项目,必须持续排除在 NuGet / GitHub Packages 发布集合之外
|
||||
- `GFramework.Cqrs.Benchmarks` 现已覆盖 request steady-state、pipeline 数量矩阵、startup、request/stream generated invoker,以及 request handler `Singleton / Transient` 生命周期矩阵
|
||||
- `GFramework.Cqrs.Benchmarks` 当前以 NuGet 方式引用 `Mediator.Abstractions` / `Mediator.SourceGenerator` `3.0.2`;`ai-libs/Mediator` 只保留为本地源码/README 对照资料,不再参与 benchmark 项目编译
|
||||
- 当前 request steady-state benchmark 已形成 baseline / `Mediator` / `MediatR` / `GFramework.Cqrs` 四方对照:约 `5.969 ns / 32 B`、`6.242 ns / 32 B`、`53.818 ns / 232 B`、`85.504 ns / 32 B`
|
||||
- 当前 request lifetime benchmark 已从旧坏值显著收敛:`Singleton` 下 `GFramework.Cqrs` 约 `84.066 ns / 32 B`(旧值 `301.731 ns / 440 B`),`Transient` 下约 `90.652 ns / 56 B`(旧值 `287.863 ns / 464 B`)
|
||||
- 当前 request steady-state benchmark 已形成 baseline / `Mediator` / `MediatR` / `GFramework.Cqrs` 四方对照:约 `5.300 ns / 32 B`、`4.964 ns / 32 B`、`57.993 ns / 232 B`、`83.823 ns / 32 B`
|
||||
- 当前 request lifetime benchmark 已从旧坏值显著收敛:`Singleton` 下 `GFramework.Cqrs` 约 `83.183 ns / 32 B`(旧值 `301.731 ns / 440 B`),`Transient` 下约 `86.243 ns / 56 B`(旧值 `287.863 ns / 464 B`)
|
||||
- 本轮已验证旧 benchmark 劣化的两个主热点:`0 pipeline` 场景下仍解析空行为列表,以及容器查询热路径在 debug 禁用时仍构造日志字符串;两者收口后,`GFramework.Cqrs` request 路径不再出现额外数百字节分配
|
||||
- 当前 request steady-state 仍落后于 source-generated `Mediator` 与 `MediatR`,但差距已从“额外数百字节分配 + 近 300ns”收敛到“零 pipeline fast-path 仍慢约 `31ns` / `3.6x` 于 `Mediator`”;下一批若继续压 request dispatch,应优先评估默认路径吸收 generated invoker/provider 的空间
|
||||
- 当前性能回归门槛已收紧为:只要改动触达 `GFramework.Cqrs` request dispatch、DI 热路径、invoker/provider、pipeline 或 benchmark 宿主,就必须至少复跑 `RequestBenchmarks.SendRequest_*` 与 `RequestLifetimeBenchmarks.SendRequest_*`
|
||||
- 当前阶段的性能验收目标已明确为:默认 request steady-state 路径不要求超过 source-generated `Mediator`,但必须持续逼近它,并至少稳定快于基于反射 / 扫描的 `MediatR`
|
||||
- `GFramework.Core` 当前已通过内部 bridge request / handler 把 legacy `ICommand`、`IAsyncCommand`、`IQuery`、`IAsyncQuery` 接到统一 `ICqrsRuntime`
|
||||
- 标准 `Architecture` 初始化路径会自动扫描 `GFramework.Core` 程序集中的 legacy bridge handler,因此旧 `SendCommand(...)` / `SendQuery(...)` 无需改变用法即可进入统一 pipeline
|
||||
- `CommandExecutor`、`QueryExecutor`、`AsyncQueryExecutor` 仍保留“无 runtime 时直接执行”的回退路径,用于不依赖容器的隔离单元测试
|
||||
@ -91,7 +94,8 @@ CQRS 迁移与收敛。
|
||||
- `RequestStartupBenchmarks` 为了量化真正的单次 cold-start,引入了 `InvocationCount=1` / `UnrollFactor=1` 的专用 job;该配置会触发 BenchmarkDotNet 的 `MinIterationTime` 提示,后续若要做稳定基线比较,还需要决定是否引入批量外层循环或自定义 cold-start harness
|
||||
- 当前 benchmark 宿主仍刻意保持“单根容器最小宿主”模型;若要公平比较 `Scoped` handler 生命周期,需要先引入显式 scope 创建与 scope 内首次解析的对照基线
|
||||
- 当前 `Mediator` 对照组仅先接入 steady-state request;若要把 `Transient` / `Scoped` 生命周期矩阵也纳入同一组对照,需要按 `Mediator` 官方 benchmark 的做法拆分 compile-time lifetime build config,而不是在同一编译产物里混用多个 lifetime
|
||||
- `BenchmarkDotNet.Artifacts/` 是本轮本地运行生成的未跟踪输出;若后续需要提交新的基准报告,应先确认仓库是否要保留该批产物,而不是默认把生成目录纳入版本控制
|
||||
- `BenchmarkDotNet.Artifacts/` 现已加入仓库忽略规则;若后续确实需要提交新的基准报告,应显式挑选结果文件或改走文档归档,而不是直接纳入整个生成目录
|
||||
- 当前 `GFramework.Cqrs` request steady-state 仍慢于 `MediatR`;在“至少超过反射版 `MediatR`”这个阶段目标达成前,任何相关改动都不能只看功能 build/test 结果,必须附带 benchmark 回归数据
|
||||
- 仓库内部仍保留旧 `Command` / `Query` API、`LegacyICqrsRuntime` alias 与部分历史命名语义,后续若不继续分批收口,容易混淆“对外替代已完成”与“内部收口未完成”
|
||||
- 若继续扩大 generated invoker 覆盖面,需要持续区分“可静态表达的合同”与 `PreciseReflectedRegistrationSpec` 等仍需保守回退的场景
|
||||
- legacy bridge 当前只为已有 `Command` / `Query` 兼容入口接到统一 request pipeline;若后续要继续对齐 `Mediator`,仍需要单独设计 stream pipeline、telemetry 与 facade 公开面,而不是把这次 bridge 当成“全部收口完成”
|
||||
@ -101,6 +105,18 @@ CQRS 迁移与收敛。
|
||||
|
||||
## 最近权威验证
|
||||
|
||||
- `dotnet build GFramework.Cqrs.Benchmarks/GFramework.Cqrs.Benchmarks.csproj -c Release`
|
||||
- 结果:通过,`0 warning / 0 error`
|
||||
- `dotnet run --project GFramework.Cqrs.Benchmarks/GFramework.Cqrs.Benchmarks.csproj -c Release -- --filter "*RequestBenchmarks.SendRequest_*" --job short --warmupCount 1 --iterationCount 1 --launchCount 1`
|
||||
- 结果:通过
|
||||
- 备注:按新性能回归门槛复跑后,steady-state request 对照约为 baseline `5.300 ns / 32 B`、`Mediator` `4.964 ns / 32 B`、`MediatR` `57.993 ns / 232 B`、`GFramework.Cqrs` `83.823 ns / 32 B`
|
||||
- `dotnet run --project GFramework.Cqrs.Benchmarks/GFramework.Cqrs.Benchmarks.csproj -c Release -- --filter "*RequestLifetimeBenchmarks.SendRequest_*" --job short --warmupCount 1 --iterationCount 1 --launchCount 1`
|
||||
- 结果:通过
|
||||
- 备注:按新性能回归门槛复跑后,`Singleton` 下 `GFramework.Cqrs` / `MediatR` 约 `83.183 ns / 32 B` vs `60.915 ns / 232 B`;`Transient` 下约 `86.243 ns / 56 B` vs `59.644 ns / 232 B`
|
||||
- `env GIT_DIR=... GIT_WORK_TREE=... python3 scripts/license-header.py --check`
|
||||
- 结果:通过
|
||||
- `git diff --check`
|
||||
- 结果:通过
|
||||
- `dotnet pack GFramework.sln -c Release --no-restore -o /tmp/gframework-pack-validation -p:IncludeSymbols=false`
|
||||
- 结果:通过
|
||||
- 备注:当前本地产物仅包含 14 个预期发布包,未生成 `GFramework.Cqrs.Benchmarks.*.nupkg`
|
||||
@ -221,9 +237,9 @@ CQRS 迁移与收敛。
|
||||
|
||||
## 下一推荐步骤
|
||||
|
||||
1. 若继续沿用 `$gframework-batch-boot 50` 且优先处理 benchmark/`Mediator` 对齐,下一批建议把 `Mediator` 的 compile-time lifetime 矩阵扩展到 `RequestLifetimeBenchmarks`,避免只有 `MediatR` 参与生命周期对照
|
||||
2. 若继续压 request steady-state 开销,下一批优先评估 `GFramework.Cqrs` 默认 request 路径吸收 generated invoker provider 的可行性,而不是只在单独 benchmark 类里保留 handwritten generated 对照
|
||||
3. 在 GitHub 上 resolve / reply 已被当前分支实质吸收的 `PR #339` stale review threads;若 head 更新后线程数量继续变化,再用 `$gframework-pr-review` 复核
|
||||
1. 若继续沿用 `$gframework-batch-boot 50` 且优先处理性能,下一批先对 `CqrsDispatcher.SendAsync(...)` / request invoker 绑定 / handler 调用适配做更细粒度热点拆分,并在每次改动后立即复跑 `RequestBenchmarks` 与 `RequestLifetimeBenchmarks`
|
||||
2. 若要把“至少超过反射版 `MediatR`”变成可执行目标,下一批优先评估默认 request 路径吸收 generated invoker/provider 或继续裁掉 dispatch binding / delegate 适配层的剩余常量开销
|
||||
3. 若 benchmark 对照需要继续贴近 `Mediator` 官方设计,再扩 `Mediator` 的 compile-time lifetime 矩阵,而不是先横向堆更多低价值场景
|
||||
|
||||
## 活跃文档
|
||||
|
||||
|
||||
@ -2,6 +2,31 @@
|
||||
|
||||
## 2026-05-08
|
||||
|
||||
### 阶段:性能回归门槛收紧与 benchmark 产物忽略收口(CQRS-REWRITE-RP-102)
|
||||
|
||||
- 延续 `RP-101` 后的 benchmark 基线,本轮没有继续改 runtime 热路径,而是先把性能治理规则补齐,避免后续优化波次出现“功能通过但 steady-state request 变慢”的回退
|
||||
- 本轮主线程决策:
|
||||
- 将 `BenchmarkDotNet.Artifacts/` 加入仓库 `.gitignore`,避免本地 benchmark 生成目录反复污染工作树
|
||||
- 在 `GFramework.Cqrs.Benchmarks/README.md` 明确写下新的默认回归门槛:只要改动触达 request dispatch、DI 热路径、invoker/provider、pipeline 或 benchmark 宿主,就必须至少复跑 `RequestBenchmarks.SendRequest_*` 与 `RequestLifetimeBenchmarks.SendRequest_*`
|
||||
- 在 `cqrs-rewrite` active tracking 中把当前阶段目标升级为“持续逼近 source-generated `Mediator`,并至少稳定超过反射版 `MediatR`”,不再只把 benchmark 当成观察工具,而是作为性能收口阶段的验收门槛
|
||||
- 本轮权威验证:
|
||||
- `dotnet build GFramework.Cqrs.Benchmarks/GFramework.Cqrs.Benchmarks.csproj -c Release`
|
||||
- 结果:通过,`0 warning / 0 error`
|
||||
- `dotnet run --project GFramework.Cqrs.Benchmarks/GFramework.Cqrs.Benchmarks.csproj -c Release -- --filter "*RequestBenchmarks.SendRequest_*" --job short --warmupCount 1 --iterationCount 1 --launchCount 1`
|
||||
- 结果:通过
|
||||
- 备注:按新门槛复跑后,steady-state request 对照约为 baseline `5.300 ns / 32 B`、`Mediator` `4.964 ns / 32 B`、`MediatR` `57.993 ns / 232 B`、`GFramework.Cqrs` `83.823 ns / 32 B`
|
||||
- `dotnet run --project GFramework.Cqrs.Benchmarks/GFramework.Cqrs.Benchmarks.csproj -c Release -- --filter "*RequestLifetimeBenchmarks.SendRequest_*" --job short --warmupCount 1 --iterationCount 1 --launchCount 1`
|
||||
- 结果:通过
|
||||
- 备注:按新门槛复跑后,`Singleton` 下 `GFramework.Cqrs` / `MediatR` 约 `83.183 ns / 32 B` vs `60.915 ns / 232 B`;`Transient` 下约 `86.243 ns / 56 B` vs `59.644 ns / 232 B`
|
||||
- `env GIT_DIR=... GIT_WORK_TREE=... python3 scripts/license-header.py --check`
|
||||
- 结果:通过
|
||||
- `git diff --check`
|
||||
- 结果:通过
|
||||
- 本轮结论:
|
||||
- `BenchmarkDotNet.Artifacts/` 现在不再是工作树噪音源
|
||||
- request benchmark 已从“偶尔人工观察”升级为 CQRS 性能波次的默认回归门槛
|
||||
- 当前离“至少超过反射版 `MediatR`”还有明确差距,所以下一批优化必须围绕 request steady-state 常量开销继续下钻,而不是只增加更多 benchmark 维度
|
||||
|
||||
### 阶段:request 热路径 benchmark 收口与 NuGet `Mediator` 对照补齐(CQRS-REWRITE-RP-101)
|
||||
|
||||
- 延续 `$gframework-batch-boot 50`,本轮先按 `origin/main` 复核 branch diff 基线:
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user