如何用BenchmarkDotNet实现预填充集合的性能测试用例
解决BenchmarkDotNet集合插入性能测试的隔离问题
针对你的需求,核心思路是一次性预生成所有初始大小的源集合并缓存,每次基准测试时复用缓存的源集合,通过副本或利用不可变特性保证测试隔离,既避免重复生成大集合的耗时,又能确保每个测试用例的初始状态干净。下面是具体实现方案:
方案1:缓存源集合+基准方法内创建副本(适用于复制开销可忽略的场景)
先在GlobalSetup中一次性生成所有需要的初始大小的集合并缓存,每次基准测试时从缓存取出源集合,创建副本后执行插入操作,这样源集合不会被修改,每个测试都是独立的。
代码示例
using BenchmarkDotNet.Attributes; using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; [MemoryDiagnoser] public class CollectionInsertBenchmarks { // 定义要测试的初始集合大小参数 [Params(0, 10, 100, 1000, 10000, 100000, 1000000, 10000000)] // 1e8内存压力过大,建议酌情测试 public int InitialSize { get; set; } // 缓存不同初始大小的源集合 private Dictionary<int, SortedDictionary<int, int>> _sortedDictCache; private Dictionary<int, SortedList<int, int>> _sortedListCache; private Dictionary<int, ImmutableSortedDictionary<int, int>> _immutableDictCache; // 预生成固定的10个插入项(用固定种子保证测试一致性) private int[] _itemsToInsert; [GlobalSetup] public void GlobalSetup() { // 预生成插入的随机项,固定种子避免每次测试结果波动 var random = new Random(42); _itemsToInsert = Enumerable.Range(0, 10).Select(_ => random.Next(int.MaxValue)).ToArray(); // 初始化缓存,每个初始大小只生成一次源集合 _sortedDictCache = new Dictionary<int, SortedDictionary<int, int>>(); _sortedListCache = new Dictionary<int, SortedList<int, int>>(); _immutableDictCache = new Dictionary<int, ImmutableSortedDictionary<int, int>>(); var testSizes = new[] { 0, 10, 100, 1000, 10000, 100000, 1000000, 10000000 }; foreach (var size in testSizes) { // 生成SortedDictionary源集合 var sortedDict = new SortedDictionary<int, int>(); for (int i = 0; i < size; i++) sortedDict.Add(i, i); _sortedDictCache[size] = sortedDict; // 生成SortedList源集合 var sortedList = new SortedList<int, int>(); for (int i = 0; i < size; i++) sortedList.Add(i, i); _sortedListCache[size] = sortedList; // 生成ImmutableSortedDictionary源集合 var immutableDict = ImmutableSortedDictionary<int, int>.Empty; for (int i = 0; i < size; i++) immutableDict = immutableDict.Add(i, i); _immutableDictCache[size] = immutableDict; } } [Benchmark] public SortedDictionary<int, int> SortedDictionary_Insert() { // 从缓存取源集合,创建副本(避免修改源缓存) var workingDict = new SortedDictionary<int, int>(_sortedDictCache[InitialSize]); foreach (var item in _itemsToInsert) workingDict.Add(item, item); return workingDict; } [Benchmark] public SortedList<int, int> SortedList_Insert() { var workingList = new SortedList<int, int>(_sortedListCache[InitialSize]); foreach (var item in _itemsToInsert) workingList.Add(item, item); return workingList; } [Benchmark] public ImmutableSortedDictionary<int, int> ImmutableSortedDictionary_Insert() { // 不可变集合无需复制,插入会返回新实例,源缓存不受影响 var workingDict = _immutableDictCache[InitialSize]; foreach (var item in _itemsToInsert) workingDict = workingDict.Add(item, item); return workingDict; } }
方案2:缓存源集合+IterationSetup复制(适用于复制开销不可忽略的场景)
如果集合太大,复制操作的时间会影响插入性能的测量,就把复制操作放到[IterationSetup]中(这个阶段的操作不会被计入基准测试的时间),基准方法只执行插入逻辑,保证测量结果纯粹。
代码示例(仅修改可变集合的测试部分)
// 在类中新增实例字段,存储每次迭代的工作集合 private SortedDictionary<int, int> _currentSortedDict; private SortedList<int, int> _currentSortedList; [IterationSetup] public void IterationSetup() { // 每次迭代前复制源集合,该操作不计入基准时间 _currentSortedDict = new SortedDictionary<int, int>(_sortedDictCache[InitialSize]); _currentSortedList = new SortedList<int, int>(_sortedListCache[InitialSize]); } [Benchmark] public void SortedDictionary_Insert() { // 只测量插入操作的时间 foreach (var item in _itemsToInsert) _currentSortedDict.Add(item, item); } [Benchmark] public void SortedList_Insert() { foreach (var item in _itemsToInsert) _currentSortedList.Add(item, item); }
关键优势
- 源集合只生成一次:所有初始大小的集合在
GlobalSetup中一次性生成,避免重复创建大集合的耗时 - 测试完全隔离:可变集合通过副本保证每次测试的初始状态干净,不可变集合利用自身特性天然隔离
- 结果稳定:预生成固定的插入项,避免随机数生成对测试结果的干扰
内容的提问来源于stack exchange,提问作者Iaman Swtrse
相关产品推荐
相关产品推荐

