ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

FASTER 基准测试指南:基于 BenchmarkDotNet 的 LightEpoch、同步/异步 API 与内联诊断测试深入解析

FASTER 基准测试指南:基于 BenchmarkDotNet 的 LightEpoch、同步/异步 API 与内联诊断测试深入解析 数据库缓存KV存储【免费下载链接】FASTERFast persistent recoverable log and key-value store cache, in C# and C.项目地址https://gitcode.com/gh_mirrors/fa/FASTER点击查看免费下载导读本文以 FASTER C# 仓库中 cs/performance/BenchmarkDotNet/README.md 为核心系统介绍 FASTER 如何借助 BenchmarkDotNet 建立微基准测试工程覆盖 epoch 机制LightEpoch的 Acquire/Release 开销微基准、同步 API 与异步 APIUpsert/RMW/Read的对比基准以及基于 InliningDiagnoser 的内联情况诊断。读完本文你将掌握该基准工程的构建与运行命令、三个现有测试的源码结构与实现细节并能仿照现有测试为 FASTER 新增自己的 BenchmarkDotNet 基准用例。一、背景为什么 FASTER 需要 BenchmarkDotNet 微基准测试FASTER 是一个高并发、持久化可恢复的日志与键值存储系统提供 C# 与 C 实现。对于这类以“低延迟、高吞吐”为核心诉求的存储组件性能回归测试至关重要——任何一次对LightEpoch、分配器或 I/O 路径的改动都可能带来几个数量级的吞吐变化。BenchmarkDotNet 是一个支持在函数上添加注解的微基准测试套件使用方式与单元测试类似通过 Attribute 标记被测方法、配置参数组合框架自动完成预热Warmup、多次迭代、统计置信区间与内存分配测量。FASTER 在仓库中维护了一个独立的 BenchmarkDotNet 测试工程位于 cs/performance/BenchmarkDotNet/ 目录目前包含三个“小而精”的基准类基准类被测主题源文件LightEpochTestsepoch 的 Acquire/ReleaseResume/Suspend开销微基准LightEpochTests.csSyncVsAsync同步 API 与异步 APIUpsert/RMW/Read性能对比SyncVsAsyncTests.csInliningTests输出哪些方法被 JIT 内联、哪些没有InliningTests.cs原 README 明确指出 “Currently we have two very simple benchmarks; more will be added later”当前只有两个很简单的基准未来会继续增加——如今工程中已经扩展到了三个基准类分别覆盖了 FASTER 性能的三个关键剖面并发原语开销、API 形态差异、以及 JIT 内联情况。二、工程结构与构建运行2.1 工程文件与依赖基准工程由 BenchmarkDotNetTests.csproj 定义Project SdkMicrosoft.NET.Sdk PropertyGroup TargetFrameworknet7.0/TargetFramework /PropertyGroup PropertyGroup PlatformTargetAnyCPU/PlatformTarget DebugTypepdbonly/DebugType DebugSymbolstrue/DebugSymbols AllowUnsafeBlockstrue/AllowUnsafeBlocks Optimizetrue/Optimize ConfigurationRelease/Configuration IsPackablefalse/IsPackable ApplicationIcon / OutputTypeExe/OutputType StartupObject / /PropertyGroup ItemGroup PackageReference IncludeBenchmarkDotNet Version0.13.2 / PackageReference IncludeBenchmarkDotNet.Diagnostics.Windows Version0.13.2 / /ItemGroup ItemGroup ProjectReference Include..\..\src\core\FASTER.core.csproj / /ItemGroup /Project关键点TargetFramework 为net7.0以当前仓库的实际配置为准运行基准前需确保本机已安装对应版本的 .NET SDK依赖两个 BenchmarkDotNet 包BenchmarkDotNet0.13.2核心框架与BenchmarkDotNet.Diagnostics.Windows0.13.2提供InliningDiagnoser等诊断器见第三节Optimizetrue/Optimize且ConfigurationRelease/Configuration微基准必须在 Release 优化模式下运行否则结果毫无参考价值AllowUnsafeBlockstrue/AllowUnsafeBlocks因为 FASTER.core 自身大量使用 unsafe 指针操作通过ProjectReference直接引用核心库 FASTER.core.csproj保证基准始终基于当前源码构建而非已安装的 NuGet 包。2.2 程序入口BenchmarkSwitcher可执行程序的入口在 BenchmarkDotNetTestsApp.cspublic static void Main(string[] args) { BenchmarkSwitcher.FromAssembly(typeof(BenchmarkDotNetTestsApp).Assembly).Run(args); }BenchmarkSwitcher.FromAssembly会自动扫描当前程序集内所有被[Benchmark]标记的类型并根据命令行参数过滤出要运行的基准。因此新增基准类后无需修改入口代码——只要新类带有[Benchmark]方法就会被 switcher 自动发现。另外该文件还定义了一个重要的公共属性BenchmarkDotNetTestsApp.cs#L11public static string TestDirectory Path.Combine(Path.GetDirectoryName(typeof(BenchmarkDotNetTestsApp).Assembly.Location), Tests);它返回程序集所在目录下的Tests子目录。SyncVsAsyncTests和InliningTests中的日志设备文件都创建在这个目录下运行完基准后由GlobalCleanup清理删除。2.3 构建与运行命令README 给出的运行步骤是构建 Release 版本Release 配置因为 BenchmarkDotNet 要求以 Release 模式运行以获得真实性能数据dotnet build -c Release cs/performance/BenchmarkDotNet/BenchmarkDotNetTests.csproj执行基准可用-f通配符过滤器只运行某一类测试。README 中给出的示例是运行 LightEpoch 相关测试bin/Release/[platform]/BenchmarkDotNetTests.exe -f *LightEpoch*[platform]取决于操作系统与目标框架如net7.0。在 Windows 上直接运行 exe在 Linux/macOS 上则可等价使用dotnet run -c Release --project cs/performance/BenchmarkDotNet/BenchmarkDotNetTests.csproj -- -f *LightEpoch*-f即--filter支持*通配符因此-f *LightEpoch*仅运行LightEpochTests-f *SyncVsAsync*仅运行SyncVsAsync-f *Inlining*仅运行InliningTests不带-f参数运行程序集中的全部基准耗时最长不推荐在调试阶段使用。查看输出BenchmarkDotNet 会打印每个基准在各参数组合下的 Mean、Error、StdDev、Gen0/Gen1/Gen2 分配等统计信息并将完整结果写入BenchmarkDotNet.Artifacts目录含 Markdown 摘要、JSON 原始数据与图表。三、LightEpochTestsepoch Acquire/Release 开销微基准3.1 基准源码LightEpochTests.cs 是三个基准中最简单、也最贴近 FASTER 并发核心的一个[GroupBenchmarksBy(BenchmarkLogicalGroupRule.ByCategory, BenchmarkLogicalGroupRule.ByParams)] public class LightEpochTests { [Params(10_000_000, 100_000_000, 1_000_000_000)] public int NumIterations; [BenchmarkCategory(LightEpoch), Benchmark] public void AcquireAndRelease() { var epoch new LightEpoch(); for (int i 0; i NumIterations; i) { epoch.Resume(); epoch.Suspend(); } } }要点解读[Params(10_000_000, 100_000_000, 1_000_000_000)]LightEpochTests.cs#L15设置三档迭代次数1000 万、1 亿、10 亿BenchmarkDotNet 会对每个参数组合各跑一轮完整测量[GroupBenchmarksBy(...)]按 BenchmarkCategory 与 Params 分组保证不同参数组合的结果在报表中清晰并列被测逻辑就是反复执行epoch.Resume(); epoch.Suspend();——即 README 中所说的 epoch 的 Acquire获取/Release释放微基准。每次循环新建一个LightEpoch实例其实会引入构造开销但本基准的意图是测量最坏情况下的每轮 Acquire/Release 成本并作为后续优化的基线。3.2 源码纵深LightEpoch 的 Acquire/Release 到底是什么LightEpoch是 FASTER 并发控制的基石实现位于 cs/src/core/Epochs/LightEpoch.cs。从基准代码中调用的Resume()和Suspend()可以看到它的两条核心路径ResumeAcquire路径LightEpoch.cs#L216-L220public void Resume() { Acquire(); ProtectAndDrain(); }SuspendRelease路径LightEpoch.cs#L206-L210public void Suspend() { Release(); if (drainCount 0) SuspendDrain(); }从源码可以推断出它的设计意图Acquire()LightEpoch.cs#L419-L429负责为当前线程在共享的 epoch 表中保留一个槽位首次调用通过ReserveEntryForThread分配之后复用[ThreadStatic]的threadEntryIndex并递增该线程的嵌套计数。注意其中带有一条Debug.Assert提示“不要在 FASTER 回调或 IDevice 实现中重入受保护的 epoch若使用 Task请采用TaskCreationOptions.RunContinuationsAsynchronously”——这正是LightEpoch使用中的典型陷阱。ProtectAndDrain()LightEpoch.cs#L188-L200把当前全局CurrentEpoch写入本线程的localCurrentEpoch槽位标记本线程“处于受保护区域”随后若drainCount 0则尝试排空待执行的回收动作。Release()LightEpoch.cs#L435-L453则清空本线程的localCurrentEpoch与threadId在嵌套计数归零后释放静态线程表槽位。SuspendDrain()LightEpoch.cs#L364-L382保证最后一个挂起Suspend的线程负责执行所有挂起的回收动作——这是“线程退出即排空”的协作式回收语义。LightEpoch的核心数据结构是一个按 64 字节缓存行对齐的Entry表LightEpoch.cs#L524-L546每项localCurrentEpochthreadId 6 个markers表大小kTableSize max(128, 2 × CPU 核数)LightEpoch.cs#L66。全局安全回收水位由ComputeNewSafeToReclaimEpochLightEpoch.cs#L339-L358扫描所有线程槽位取“最老进行中调用”的前一代计算得出。这些机制共同保证了任何内存回收动作都只能发生在所有线程都离开旧 epoch 之后这正是无锁哈希索引中“延迟回收deferred reclamation”的根基。此外LightEpoch还通过BumpCurrentEpoch(Action onDrain)LightEpoch.cs#L243-L290将回收动作与特定 epoch 关联并在其上构建了EpochProtectedVersionSchemeEPVS见 cs/src/core/Epochs/EpochProtectedVersionScheme.cs用于 checkpoint 状态机等版本化操作。整个LightEpoch类被FASTER.core中分配器AllocatorBase.cs#L50、扫描迭代器ScanIteratorBase.cs#L33、存储设备StorageDeviceBase.cs#L63等多处持有——因此它的 Acquire/Release 开销直接进入 FASTER 每一条读写热路径对该路径做微基准的意义不言而喻。四、SyncVsAsyncTests同步与异步 API 性能对比4.1 基准源码结构SyncVsAsyncTests.cs 对比了 FASTER 会话 API 的同步与异步形态覆盖三大操作类别InsertUpsert、RMWRead-Modify-Write、Read。其参数为记录数[Params(100, 1_000_000)] public int NumRecords;SyncVsAsyncTests.cs#L18存储准备SyncVsAsyncTests.cs#L28-L38void SetupStore() { logDirectory BenchmarkDotNetTestsApp.TestDirectory; var logFilename Path.Combine(logDirectory, ${nameof(SyncVsAsync)}_{Guid.NewGuid()}.log); logDevice Devices.CreateLogDevice(logFilename, preallocateFile: true, deleteOnClose: true, useIoCompletionPort: true); var logSettings new LogSettings { LogDevice logDevice }; store new FasterKVlong, long(1L 20, logSettings); }几个值得注意的实操细节Devices.CreateLogDevice其完整签名见 cs/src/core/Device/Devices.cs#L29是 FASTER 的设备工厂方法这里使用了preallocateFile: true预分配日志文件、deleteOnClose: true关闭即删除适合基准场景避免残留文件、useIoCompletionPort: trueWindows 上使用 IOCP 轮询完成 IOnew FasterKVlong, long(1L 20, logSettings)创建哈希表大小为 2^20 的 FasterKV 实例键值类型均为longblittable走无托管 GC 的快路径[GlobalSetup]与[GlobalCleanup]是 BenchmarkDotNet 的生命周期钩子SetupEmptyStore只创建空存储服务于 Insert 类基准SetupPopulatedStore额外用同步 Upsert 预先填充NumRecords条记录服务于 RMW/Read 类基准TearDown负责 Dispose 存储与设备并删除日志目录。同步填充与异步填充SyncVsAsyncTests.cs#L41-L56void PopulateStoreSync() { using var session store.For(new SimpleFunctionslong, long()).NewSessionSimpleFunctionslong, long(); for (long ii 0; ii NumRecords; ii) session.Upsert(ii, ii); } async ValueTask PopulateStoreAsync() { using var session store.For(new SimpleFunctionslong, long()).NewSessionSimpleFunctionslong, long(); for (long ii 0; ii NumRecords; ii) { var result await session.UpsertAsync(ii, ii); while (result.Status.IsPending) result await result.CompleteAsync().ConfigureAwait(false); } }这里展示了 FASTER 异步 API 的标准 pending 处理模式UpsertAsync/RMWAsync/ReadAsync返回的结果带有Status当操作落到磁盘/待处理队列时会进入IsPending状态必须循环调用CompleteAsync()直到完成。这一循环是 FASTER 异步会话编程的核心范式在仓库的 Async 实现如 cs/src/core/Async/UpsertAsync.cs中可以看到其对应的状态推进逻辑。三类对比基准SyncVsAsyncTests.cs#L82-L133[BenchmarkCategory(Insert), Benchmark(Baseline true)] public void InsertSync() PopulateStoreSync(); [BenchmarkCategory(Insert), Benchmark] public ValueTask InsertAsync() PopulateStoreAsync(); [BenchmarkCategory(RMW), Benchmark(Baseline true)] public void RMWSync() { ... session.RMW(ii, ii * 2); session.CompletePending(); } [BenchmarkCategory(RMW), Benchmark] public async ValueTask RMWAsync() { ... } [BenchmarkCategory(Read), Benchmark(Baseline true)] public void ReadSync() { ... 若 IsPending 则 CompletePendingWithOutputs ... } [BenchmarkCategory(Read), Benchmark] public async ValueTask ReadAsync() { ... }每个类别都以同步版本作为Baseline true异步版本与之对比——BenchmarkDotNet 报表会直接给出异步相对同步的比值Ratio回答“异步 API 是否有额外开销、开销多大”这一关键工程问题。同步路径中的session.CompletePending()、CompletePendingWithOutputs(out var completedOutputs, wait: true)是同步会话补齐 pending 操作的标准收尾方式。值得指出的是源码中被注释掉的[ParamsAllValues] bool useAsync;SyncVsAsyncTests.cs#L21-L22表明作者曾设想把同步/异步也做成参数组合维度最终选择了分别写独立 Benchmark 方法的方式——这也是 BenchmarkDotNet 中对比两种 API 形态的推荐做法。被测函数SimpleFunctionslong, long定义于 cs/src/core/Index/Interfaces/FunctionsBase.cs#L121-L124是内置的最小函数集实现。五、InliningTestsJIT 内联诊断5.1 基准源码InliningTests.cs 的目标不是测吞吐而是输出 FASTER.core 中哪些方法被 JIT 内联、哪些没有——内联决策直接影响热路径的调用开销与寄存器压力是性能工程师排查“为什么优化没生效”的重要工具。[InliningDiagnoser(logFailuresOnly: false, allowedNamespaces: new[] { FASTER.core })] [GroupBenchmarksBy(BenchmarkLogicalGroupRule.ByCategory, BenchmarkLogicalGroupRule.ByParams)] public class InliningTests { [Params(1_000_000)] public int NumRecords; ... }InliningTests.cs#L15-L19关键点[InliningDiagnoser(...)]来自BenchmarkDotNet.Diagnostics.Windows包这也解释了 csproj 中为何要引入该包。logFailuresOnly: false表示同时记录成功与失败的内联allowedNamespaces: new[] { FASTER.core }将诊断范围限定在 FASTER.core 命名空间避免被无关框架代码刷屏它只跑 100 万条记录的三个操作Upsert、RMW、ReadInliningTests.cs#L66-L91与SyncVsAsync共享同一套SetupStore/PopulateStore模式说明这两个基准类针对同一批操作从不同视角分析。运行后InliningDiagnoser 会为每个被测方法生成 HTML 报告标注每个调用点的内联结果成功/失败及原因如“too big”“has locals”等。FASTER 源码中大量使用了[MethodImpl(MethodImplOptions.AggressiveInlining)]例如LightEpoch的Resume/Suspend/ProtectAndDrain见 LightEpoch.cs#L187-L220这份报告能直观验证这些注解是否按预期生效。5.2 三个基准的协同意义把三个基准放在一起看它们构成了 FASTER 性能工程的完整闭环LightEpochTests回答“并发原语本身多快”SyncVsAsyncTests回答“API 形态选择带来多大差异”InliningTests回答“关键路径代码是否被编译器友好对待”。任何一个维度出现问题都能在对应基准中暴露出来。六、跨运行比较的限制与应对README 明确记录了一个 BenchmarkDotNet 的已知限制BenchmarkDotNet does not support cross-run comparisons; currently, just save the output to a file and diff. A script will be written in the future to automate these comparisons.即BenchmarkDotNet 不支持跨多次运行的直接对比。这是因为每次运行的环境机器负载、JIT 决策、ASLR 等都不同官方工具只保证单次运行内部各基准之间的可比性。当前仓库的做法是每次运行后把 BenchmarkDotNet 输出的 Markdown/JSON 结果保存为文件BenchmarkDotNet 默认输出到BenchmarkDotNet.Artifacts通过 diff 工具对比改动前后的结果文件未来计划编写脚本自动化这一对比流程README 原话可作为仓库 roadmap 的参考。这也解释了为何 README 中给的是-f *LightEpoch*这类带过滤器的运行方式——每次聚焦单一基准、保存结果、再做跨版本 diff是当前约束下的推荐工作流。七、如何新增一个基准测试README 给出了新增基准的指引“以现有测试为范例并在本文档中简要登记新增条目”。结合本工程的既有模式新增一个基准的推荐步骤是在 cs/performance/BenchmarkDotNet/ 目录下新建YourFeatureTests.cs类上加[GroupBenchmarksBy(BenchmarkLogicalGroupRule.ByCategory, BenchmarkLogicalGroupRule.ByParams)]用[Params(...)]声明关键变量如记录数、线程数用[BenchmarkCategory(...)] [Benchmark]声明被测方法若想作为对照组加[Benchmark(Baseline true)]若涉及 FasterKV 实例复用SetupStore/PopulateStore/TearDown模式GlobalSetup/GlobalCleanup日志设备文件统一放在BenchmarkDotNetTestsApp.TestDirectory下并deleteOnClose: true无需修改入口BenchmarkSwitcher.FromAssembly会自动发现新基准类BenchmarkDotNetTestsApp.cs#L15运行验证dotnet build -c Release后执行-f *YourFeature*确认能正常出结果按 README 约定在 README.md 的测试清单中补一行简要说明。八、小结FASTER 的 BenchmarkDotNet 基准工程虽然规模不大却精准覆盖了存储引擎性能的三个决定性剖面epoch 并发原语LightEpoch的单次 Acquire/Release 开销、同步与异步会话 API 的开销差、以及 FASTER.core 热路径方法的 JIT 内联情况。工程结构switcher 入口 按主题拆分的基准类 共享的日志设备管理模式让新增基准的成本极低。对 FASTER 的贡献者而言这套工程是评估核心库改动性能影响的标准工具对研究 FASTER 实现原理的读者而言从 LightEpochTests.cs 出发回溯 LightEpoch.cs 的 Acquire/Release 与 drain 机制是理解其无锁并发回收设计的绝佳入口。参考路径索引基准工程 READMEcs/performance/BenchmarkDotNet/README.md入口与工程配置BenchmarkDotNetTestsApp.cs、BenchmarkDotNetTests.csproj三个基准类LightEpochTests.cs、SyncVsAsyncTests.cs、InliningTests.cs被测核心实现LightEpoch.cs、EpochProtectedVersionScheme.cs、Devices.cs、FunctionsBase.cs赞分享数据库缓存KV存储【免费下载链接】FASTERFast persistent recoverable log and key-value store cache, in C# and C.项目地址https://gitcode.com/gh_mirrors/fa/FASTER点击查看免费下载相关推荐如何用 Rufus 免费制作 Windows 11 启动 U 盘绕过 TPM 限制的完整指南如何用 Rufus 免费制作 Windows 11 启动 U 盘绕过 TPM 限制的完整指南 升级 Windows 11 时安装程序提示此电脑不满足最低系桌面应用开发工具BenchmarkDotNet 基准测试 IAsyncEnumerableT迭代驱动、取消传播与 Disassembly 诊断完全指南BenchmarkDotNet 基准测试 IAsyncEnumerableT 迭代驱动、取消传播与 Disassembly 诊断完全指南 Benchmark性能测试开发工具3分钟掌握微信防撤回补丁永久保存重要信息不丢失3分钟掌握微信防撤回补丁永久保存重要信息不丢失 在日常使用微信、QQ和TIM等即时通讯工具时你是否经常遇到重要消息被对方撤回而无法查看的困扰无论是工作中的桌面应用即时通讯上一篇wiliwili解锁游戏主机的B站娱乐新体验下一篇如何用Zotero-mdnotes实现高效文献管理与知识自动化创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表