GFramework/GFramework.Cqrs.Benchmarks/Messaging/RequestLifetimeBenchmarks.cs
gewuyou 1420bd4340 fix(cqrs): 修复 benchmark scoped 宿主与 PR 恢复入口
- 修复 benchmark 入口的 artifacts 隔离判断与嵌套宿主目录保护,避免并发运行时宿主目录递归膨胀

- 优化 request 与 stream 生命周期 benchmark 的 scoped 宿主模型,复用单个 runtime 并仅在每次调用时创建真实 DI scope

- 补充 ScopedBenchmarkContainer 的 XML 合同说明,并收敛 README 与 ai-plan active 入口到当前 PR 恢复点
2026-05-11 10:50:50 +08:00

283 lines
10 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// 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>
/// 对比 request steady-state dispatch 在不同 handler 生命周期下的额外开销。
/// </summary>
/// <remarks>
/// 当前矩阵覆盖 `Singleton`、`Scoped` 与 `Transient`。
/// 其中 `Scoped` 会在每次 request 分发时显式创建并释放真实的 DI 作用域,
/// 避免把 scoped handler 错误地压到根容器解析而扭曲生命周期对照。
/// </remarks>
[Config(typeof(Config))]
public class RequestLifetimeBenchmarks
{
private MicrosoftDiContainer _container = null!;
private ICqrsRuntime? _runtime;
private ScopedBenchmarkContainer? _scopedContainer;
private ICqrsRuntime? _scopedRuntime;
private ServiceProvider _serviceProvider = null!;
private IMediator? _mediatr;
private BenchmarkRequestHandler _baselineHandler = null!;
private BenchmarkRequest _request = null!;
private ILogger _runtimeLogger = null!;
/// <summary>
/// 控制当前 benchmark 使用的 handler 生命周期。
/// </summary>
[Params(HandlerLifetime.Singleton, HandlerLifetime.Scoped, HandlerLifetime.Transient)]
public HandlerLifetime Lifetime { get; set; }
/// <summary>
/// 可公平比较的 benchmark handler 生命周期集合。
/// </summary>
public enum HandlerLifetime
{
/// <summary>
/// 复用单个 handler 实例。
/// </summary>
Singleton,
/// <summary>
/// 每次 request 在显式作用域内解析并复用 handler 实例。
/// </summary>
Scoped,
/// <summary>
/// 每次分发都重新解析新的 handler 实例。
/// </summary>
Transient
}
/// <summary>
/// 配置 request lifetime benchmark 的公共输出格式。
/// </summary>
private sealed class Config : ManualConfig
{
public Config()
{
AddJob(Job.Default);
AddColumnProvider(DefaultColumnProviders.Instance);
AddColumn(new CustomColumn("Scenario", static (_, _) => "RequestLifetime"));
AddDiagnoser(MemoryDiagnoser.Default);
WithOrderer(new DefaultOrderer(SummaryOrderPolicy.FastestToSlowest, MethodOrderPolicy.Declared));
}
}
/// <summary>
/// 构建当前生命周期下的 GFramework 与 MediatR request 对照宿主。
/// </summary>
[GlobalSetup]
public void Setup()
{
LoggerFactoryResolver.Provider = new ConsoleLoggerFactoryProvider
{
MinLevel = LogLevel.Fatal
};
Fixture.Setup($"RequestLifetime/{Lifetime}", handlerCount: 1, pipelineCount: 0);
BenchmarkDispatcherCacheHelper.ClearDispatcherCaches();
_baselineHandler = new BenchmarkRequestHandler();
_request = new BenchmarkRequest(Guid.NewGuid());
_runtimeLogger = LoggerFactoryResolver.Provider.CreateLogger(nameof(RequestLifetimeBenchmarks) + "." + Lifetime);
_container = BenchmarkHostFactory.CreateFrozenGFrameworkContainer(container =>
{
BenchmarkHostFactory.RegisterGeneratedBenchmarkRegistry<GeneratedRequestLifetimeBenchmarkRegistry>(container);
RegisterGFrameworkHandler(container, Lifetime);
});
// 容器内已提前保留默认 runtime 以支撑 generated registry 接线;
// 这里额外创建带生命周期后缀的 runtime只是为了区分不同 benchmark 矩阵的 dispatcher 日志。
if (Lifetime != HandlerLifetime.Scoped)
{
_runtime = GFramework.Cqrs.CqrsRuntimeFactory.CreateRuntime(
_container,
_runtimeLogger);
}
else
{
_scopedContainer = new ScopedBenchmarkContainer(_container);
_scopedRuntime = GFramework.Cqrs.CqrsRuntimeFactory.CreateRuntime(
_scopedContainer,
_runtimeLogger);
}
_serviceProvider = BenchmarkHostFactory.CreateMediatRServiceProvider(
configure: null,
typeof(RequestLifetimeBenchmarks),
static candidateType => candidateType == typeof(BenchmarkRequestHandler),
ResolveMediatRLifetime(Lifetime));
if (Lifetime != HandlerLifetime.Scoped)
{
_mediatr = _serviceProvider.GetRequiredService<IMediator>();
}
}
/// <summary>
/// 释放当前生命周期矩阵持有的 benchmark 宿主资源。
/// </summary>
[GlobalCleanup]
public void Cleanup()
{
try
{
BenchmarkCleanupHelper.DisposeAll(_container, _serviceProvider);
}
finally
{
BenchmarkDispatcherCacheHelper.ClearDispatcherCaches();
}
}
/// <summary>
/// 直接调用 handler作为不同生命周期矩阵下的 dispatch 额外开销 baseline。
/// </summary>
[Benchmark(Baseline = true)]
public ValueTask<BenchmarkResponse> SendRequest_Baseline()
{
return _baselineHandler.Handle(_request, CancellationToken.None);
}
/// <summary>
/// 通过 GFramework.CQRS runtime 发送 request。
/// </summary>
[Benchmark]
public ValueTask<BenchmarkResponse> SendRequest_GFrameworkCqrs()
{
if (Lifetime == HandlerLifetime.Scoped)
{
return BenchmarkHostFactory.SendScopedGFrameworkRequestAsync(
_scopedRuntime!,
_scopedContainer!,
BenchmarkContext.Instance,
_request,
CancellationToken.None);
}
return _runtime!.SendAsync(BenchmarkContext.Instance, _request, CancellationToken.None);
}
/// <summary>
/// 通过 MediatR 发送 request作为外部对照。
/// </summary>
[Benchmark]
public Task<BenchmarkResponse> SendRequest_MediatR()
{
if (Lifetime == HandlerLifetime.Scoped)
{
return BenchmarkHostFactory.SendScopedMediatRRequestAsync(
_serviceProvider,
_request,
CancellationToken.None);
}
return _mediatr!.Send(_request, CancellationToken.None);
}
/// <summary>
/// 按生命周期把 benchmark request handler 注册到 GFramework 容器。
/// </summary>
/// <param name="container">当前 benchmark 拥有并负责释放的容器。</param>
/// <param name="lifetime">待比较的 handler 生命周期。</param>
/// <remarks>
/// 先通过 generated registry 提供静态 descriptor再显式覆盖 handler 生命周期,
/// 可以把比较变量收敛到 handler 解析成本,而不是 descriptor 发现路径本身。
/// </remarks>
private static void RegisterGFrameworkHandler(MicrosoftDiContainer container, HandlerLifetime lifetime)
{
ArgumentNullException.ThrowIfNull(container);
switch (lifetime)
{
case HandlerLifetime.Singleton:
container.RegisterSingleton<GFramework.Cqrs.Abstractions.Cqrs.IRequestHandler<BenchmarkRequest, BenchmarkResponse>, BenchmarkRequestHandler>();
return;
case HandlerLifetime.Scoped:
container.RegisterScoped<GFramework.Cqrs.Abstractions.Cqrs.IRequestHandler<BenchmarkRequest, BenchmarkResponse>, BenchmarkRequestHandler>();
return;
case HandlerLifetime.Transient:
container.RegisterTransient<GFramework.Cqrs.Abstractions.Cqrs.IRequestHandler<BenchmarkRequest, BenchmarkResponse>, BenchmarkRequestHandler>();
return;
default:
throw new ArgumentOutOfRangeException(nameof(lifetime), lifetime, "Unsupported benchmark handler lifetime.");
}
}
/// <summary>
/// 将 benchmark 生命周期映射为 MediatR 组装所需的 <see cref="ServiceLifetime" />。
/// </summary>
/// <param name="lifetime">待比较的 handler 生命周期。</param>
private static ServiceLifetime ResolveMediatRLifetime(HandlerLifetime lifetime)
{
return lifetime switch
{
HandlerLifetime.Singleton => ServiceLifetime.Singleton,
HandlerLifetime.Scoped => ServiceLifetime.Scoped,
HandlerLifetime.Transient => ServiceLifetime.Transient,
_ => throw new ArgumentOutOfRangeException(nameof(lifetime), lifetime, "Unsupported benchmark handler lifetime.")
};
}
/// <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));
}
}
}