using BenchmarkDotNet.Attributes;
namespace Apq.Cfg.Benchmarks;
///
/// 缓存效果性能基准测试
/// 测试热路径重复读取、缓存命中/未命中等场景
///
[Config(typeof(BenchmarkConfig))]
public class CacheBenchmarks : IDisposable
{
private readonly string _testDir;
private ICfgRoot _cfg = null!;
// 预定义的键,避免在测试中动态生成
private readonly string[] _existingKeys = new string[100];
private readonly string[] _nonExistingKeys = new string[100];
public CacheBenchmarks()
{
_testDir = Path.Combine(Path.GetTempPath(), $"ApqCfgBench_{Guid.NewGuid():N}");
Directory.CreateDirectory(_testDir);
// 预生成键
for (int i = 0; i < 100; i++)
{
_existingKeys[i] = $"Data:Key{i}";
_nonExistingKeys[i] = $"NonExistent:Key{i}";
}
}
[GlobalSetup]
public void Setup()
{
var jsonPath = Path.Combine(_testDir, "config.json");
// 生成包含 100 个键的配置
var content = "{\n \"Data\": {\n";
for (int i = 0; i < 100; i++)
{
content += $" \"Key{i}\": \"Value{i}\"";
if (i < 99) content += ",";
content += "\n";
}
content += " },\n \"HotKey\": \"HotValue\"\n}";
File.WriteAllText(jsonPath, content);
_cfg = new CfgBuilder()
.AddJson(jsonPath, level: 0, writeable: true, isPrimaryWriter: true)
.Build();
}
[GlobalCleanup]
public void Cleanup()
{
Dispose();
}
public void Dispose()
{
_cfg?.Dispose();
if (Directory.Exists(_testDir))
{
Directory.Delete(_testDir, true);
}
}
#region 热路径重复读取(缓存命中)
///
/// 同一键连续读取 1000 次(测试缓存效果)
///
[Benchmark(Baseline = true)]
[BenchmarkCategory("HotPath")]
public void HotPath_SameKey_1000()
{
for (int i = 0; i < 1000; i++)
{
_ = _cfg.Get("HotKey");
}
}
///
/// 同一键连续读取 10000 次
///
[Benchmark]
[BenchmarkCategory("HotPath")]
public void HotPath_SameKey_10000()
{
for (int i = 0; i < 10000; i++)
{
_ = _cfg.Get("HotKey");
}
}
///
/// 两个键交替读取(模拟常见的双键热路径)
///
[Benchmark]
[BenchmarkCategory("HotPath")]
public void HotPath_TwoKeys_Alternating()
{
for (int i = 0; i < 1000; i++)
{
_ = _cfg.Get("HotKey");
_ = _cfg.Get("Data:Key0");
}
}
///
/// 少量键循环读取(模拟热点配置)
///
[Benchmark]
[BenchmarkCategory("HotPath")]
public void HotPath_FewKeys_Loop()
{
for (int i = 0; i < 1000; i++)
{
_ = _cfg.Get("Data:Key0");
_ = _cfg.Get("Data:Key1");
_ = _cfg.Get("Data:Key2");
_ = _cfg.Get("Data:Key3");
_ = _cfg.Get("Data:Key4");
}
}
#endregion
#region 缓存未命中(不存在的键)
///
/// 读取不存在的键 1000 次
///
[Benchmark]
[BenchmarkCategory("CacheMiss")]
public void CacheMiss_NonExistentKey_1000()
{
for (int i = 0; i < 1000; i++)
{
_ = _cfg.Get("NonExistent:Key");
}
}
///
/// 读取不同的不存在键(每次不同)
///
[Benchmark]
[BenchmarkCategory("CacheMiss")]
public void CacheMiss_DifferentNonExistentKeys()
{
for (int i = 0; i < 100; i++)
{
_ = _cfg.Get(_nonExistingKeys[i]);
}
}
///
/// 混合存在和不存在的键
///
[Benchmark]
[BenchmarkCategory("CacheMiss")]
public void CacheMiss_MixedExistence()
{
for (int i = 0; i < 100; i++)
{
_ = _cfg.Get(_existingKeys[i]);
_ = _cfg.Get(_nonExistingKeys[i]);
}
}
#endregion
#region 冷路径(不同键访问)
///
/// 顺序访问 100 个不同的键
///
[Benchmark]
[BenchmarkCategory("ColdPath")]
public void ColdPath_Sequential_100Keys()
{
for (int i = 0; i < 100; i++)
{
_ = _cfg.Get(_existingKeys[i]);
}
}
///
/// 随机访问模式(模拟真实场景)
///
[Benchmark]
[BenchmarkCategory("ColdPath")]
public void ColdPath_Random_Pattern()
{
// 使用固定的"随机"模式,确保可重复性
int[] pattern = { 42, 7, 91, 23, 56, 3, 78, 15, 67, 34,
89, 12, 45, 0, 99, 28, 61, 8, 73, 50 };
for (int round = 0; round < 50; round++)
{
foreach (var idx in pattern)
{
_ = _cfg.Get(_existingKeys[idx]);
}
}
}
#endregion
#region Exists 缓存效果
///
/// 同一键 Exists 检查 1000 次
///
[Benchmark]
[BenchmarkCategory("ExistsCache")]
public void Exists_SameKey_1000()
{
for (int i = 0; i < 1000; i++)
{
_ = _cfg.Exists("HotKey");
}
}
///
/// 不存在键 Exists 检查 1000 次
///
[Benchmark]
[BenchmarkCategory("ExistsCache")]
public void Exists_NonExistentKey_1000()
{
for (int i = 0; i < 1000; i++)
{
_ = _cfg.Exists("NonExistent:Key");
}
}
///
/// 混合 Exists 检查
///
[Benchmark]
[BenchmarkCategory("ExistsCache")]
public void Exists_Mixed_1000()
{
for (int i = 0; i < 500; i++)
{
_ = _cfg.Exists("HotKey");
_ = _cfg.Exists("NonExistent:Key");
}
}
#endregion
#region 写入后读取(缓存失效场景)
///
/// 写入后立即读取同一键
///
[Benchmark]
[BenchmarkCategory("WriteInvalidation")]
public void WriteAndRead_SameKey()
{
for (int i = 0; i < 100; i++)
{
_cfg.Set("Temp:Key", $"Value{i}");
_ = _cfg.Get("Temp:Key");
}
}
///
/// 写入后读取不同键(测试写入是否影响其他键的缓存)
///
[Benchmark]
[BenchmarkCategory("WriteInvalidation")]
public void WriteAndRead_DifferentKeys()
{
for (int i = 0; i < 100; i++)
{
_cfg.Set($"Temp:Key{i}", $"Value{i}");
_ = _cfg.Get("HotKey");
}
}
///
/// 批量写入后批量读取
///
[Benchmark]
[BenchmarkCategory("WriteInvalidation")]
public void BatchWrite_ThenBatchRead()
{
// 批量写入
for (int i = 0; i < 100; i++)
{
_cfg.Set($"Batch:Key{i}", $"Value{i}");
}
// 批量读取
for (int i = 0; i < 100; i++)
{
_ = _cfg.Get($"Batch:Key{i}");
}
}
#endregion
#region 首次访问 vs 后续访问
///
/// 首次访问新键(冷启动)
///
[Benchmark]
[BenchmarkCategory("FirstAccess")]
public string? FirstAccess_NewKey()
{
return _cfg.Get("Data:Key50");
}
///
/// 后续访问(预热后)- 通过多次调用模拟
///
[Benchmark]
[BenchmarkCategory("FirstAccess")]
public void SubsequentAccess_Warmed()
{
// 预热
_ = _cfg.Get("Data:Key50");
// 后续访问
for (int i = 0; i < 100; i++)
{
_ = _cfg.Get("Data:Key50");
}
}
#endregion
}