CacheBenchmarks.cs 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337
  1. using BenchmarkDotNet.Attributes;
  2. namespace Apq.Cfg.Benchmarks;
  3. /// <summary>
  4. /// 缓存效果性能基准测试
  5. /// 测试热路径重复读取、缓存命中/未命中等场景
  6. /// </summary>
  7. [Config(typeof(BenchmarkConfig))]
  8. public class CacheBenchmarks : IDisposable
  9. {
  10. private readonly string _testDir;
  11. private ICfgRoot _cfg = null!;
  12. // 预定义的键,避免在测试中动态生成
  13. private readonly string[] _existingKeys = new string[100];
  14. private readonly string[] _nonExistingKeys = new string[100];
  15. public CacheBenchmarks()
  16. {
  17. _testDir = Path.Combine(Path.GetTempPath(), $"ApqCfgBench_{Guid.NewGuid():N}");
  18. Directory.CreateDirectory(_testDir);
  19. // 预生成键
  20. for (int i = 0; i < 100; i++)
  21. {
  22. _existingKeys[i] = $"Data:Key{i}";
  23. _nonExistingKeys[i] = $"NonExistent:Key{i}";
  24. }
  25. }
  26. [GlobalSetup]
  27. public void Setup()
  28. {
  29. var jsonPath = Path.Combine(_testDir, "config.json");
  30. // 生成包含 100 个键的配置
  31. var content = "{\n \"Data\": {\n";
  32. for (int i = 0; i < 100; i++)
  33. {
  34. content += $" \"Key{i}\": \"Value{i}\"";
  35. if (i < 99) content += ",";
  36. content += "\n";
  37. }
  38. content += " },\n \"HotKey\": \"HotValue\"\n}";
  39. File.WriteAllText(jsonPath, content);
  40. _cfg = new CfgBuilder()
  41. .AddJson(jsonPath, level: 0, writeable: true, isPrimaryWriter: true)
  42. .Build();
  43. }
  44. [GlobalCleanup]
  45. public void Cleanup()
  46. {
  47. Dispose();
  48. }
  49. public void Dispose()
  50. {
  51. _cfg?.Dispose();
  52. if (Directory.Exists(_testDir))
  53. {
  54. Directory.Delete(_testDir, true);
  55. }
  56. }
  57. #region 热路径重复读取(缓存命中)
  58. /// <summary>
  59. /// 同一键连续读取 1000 次(测试缓存效果)
  60. /// </summary>
  61. [Benchmark(Baseline = true)]
  62. [BenchmarkCategory("HotPath")]
  63. public void HotPath_SameKey_1000()
  64. {
  65. for (int i = 0; i < 1000; i++)
  66. {
  67. _ = _cfg.Get("HotKey");
  68. }
  69. }
  70. /// <summary>
  71. /// 同一键连续读取 10000 次
  72. /// </summary>
  73. [Benchmark]
  74. [BenchmarkCategory("HotPath")]
  75. public void HotPath_SameKey_10000()
  76. {
  77. for (int i = 0; i < 10000; i++)
  78. {
  79. _ = _cfg.Get("HotKey");
  80. }
  81. }
  82. /// <summary>
  83. /// 两个键交替读取(模拟常见的双键热路径)
  84. /// </summary>
  85. [Benchmark]
  86. [BenchmarkCategory("HotPath")]
  87. public void HotPath_TwoKeys_Alternating()
  88. {
  89. for (int i = 0; i < 1000; i++)
  90. {
  91. _ = _cfg.Get("HotKey");
  92. _ = _cfg.Get("Data:Key0");
  93. }
  94. }
  95. /// <summary>
  96. /// 少量键循环读取(模拟热点配置)
  97. /// </summary>
  98. [Benchmark]
  99. [BenchmarkCategory("HotPath")]
  100. public void HotPath_FewKeys_Loop()
  101. {
  102. for (int i = 0; i < 1000; i++)
  103. {
  104. _ = _cfg.Get("Data:Key0");
  105. _ = _cfg.Get("Data:Key1");
  106. _ = _cfg.Get("Data:Key2");
  107. _ = _cfg.Get("Data:Key3");
  108. _ = _cfg.Get("Data:Key4");
  109. }
  110. }
  111. #endregion
  112. #region 缓存未命中(不存在的键)
  113. /// <summary>
  114. /// 读取不存在的键 1000 次
  115. /// </summary>
  116. [Benchmark]
  117. [BenchmarkCategory("CacheMiss")]
  118. public void CacheMiss_NonExistentKey_1000()
  119. {
  120. for (int i = 0; i < 1000; i++)
  121. {
  122. _ = _cfg.Get("NonExistent:Key");
  123. }
  124. }
  125. /// <summary>
  126. /// 读取不同的不存在键(每次不同)
  127. /// </summary>
  128. [Benchmark]
  129. [BenchmarkCategory("CacheMiss")]
  130. public void CacheMiss_DifferentNonExistentKeys()
  131. {
  132. for (int i = 0; i < 100; i++)
  133. {
  134. _ = _cfg.Get(_nonExistingKeys[i]);
  135. }
  136. }
  137. /// <summary>
  138. /// 混合存在和不存在的键
  139. /// </summary>
  140. [Benchmark]
  141. [BenchmarkCategory("CacheMiss")]
  142. public void CacheMiss_MixedExistence()
  143. {
  144. for (int i = 0; i < 100; i++)
  145. {
  146. _ = _cfg.Get(_existingKeys[i]);
  147. _ = _cfg.Get(_nonExistingKeys[i]);
  148. }
  149. }
  150. #endregion
  151. #region 冷路径(不同键访问)
  152. /// <summary>
  153. /// 顺序访问 100 个不同的键
  154. /// </summary>
  155. [Benchmark]
  156. [BenchmarkCategory("ColdPath")]
  157. public void ColdPath_Sequential_100Keys()
  158. {
  159. for (int i = 0; i < 100; i++)
  160. {
  161. _ = _cfg.Get(_existingKeys[i]);
  162. }
  163. }
  164. /// <summary>
  165. /// 随机访问模式(模拟真实场景)
  166. /// </summary>
  167. [Benchmark]
  168. [BenchmarkCategory("ColdPath")]
  169. public void ColdPath_Random_Pattern()
  170. {
  171. // 使用固定的"随机"模式,确保可重复性
  172. int[] pattern = { 42, 7, 91, 23, 56, 3, 78, 15, 67, 34,
  173. 89, 12, 45, 0, 99, 28, 61, 8, 73, 50 };
  174. for (int round = 0; round < 50; round++)
  175. {
  176. foreach (var idx in pattern)
  177. {
  178. _ = _cfg.Get(_existingKeys[idx]);
  179. }
  180. }
  181. }
  182. #endregion
  183. #region Exists 缓存效果
  184. /// <summary>
  185. /// 同一键 Exists 检查 1000 次
  186. /// </summary>
  187. [Benchmark]
  188. [BenchmarkCategory("ExistsCache")]
  189. public void Exists_SameKey_1000()
  190. {
  191. for (int i = 0; i < 1000; i++)
  192. {
  193. _ = _cfg.Exists("HotKey");
  194. }
  195. }
  196. /// <summary>
  197. /// 不存在键 Exists 检查 1000 次
  198. /// </summary>
  199. [Benchmark]
  200. [BenchmarkCategory("ExistsCache")]
  201. public void Exists_NonExistentKey_1000()
  202. {
  203. for (int i = 0; i < 1000; i++)
  204. {
  205. _ = _cfg.Exists("NonExistent:Key");
  206. }
  207. }
  208. /// <summary>
  209. /// 混合 Exists 检查
  210. /// </summary>
  211. [Benchmark]
  212. [BenchmarkCategory("ExistsCache")]
  213. public void Exists_Mixed_1000()
  214. {
  215. for (int i = 0; i < 500; i++)
  216. {
  217. _ = _cfg.Exists("HotKey");
  218. _ = _cfg.Exists("NonExistent:Key");
  219. }
  220. }
  221. #endregion
  222. #region 写入后读取(缓存失效场景)
  223. /// <summary>
  224. /// 写入后立即读取同一键
  225. /// </summary>
  226. [Benchmark]
  227. [BenchmarkCategory("WriteInvalidation")]
  228. public void WriteAndRead_SameKey()
  229. {
  230. for (int i = 0; i < 100; i++)
  231. {
  232. _cfg.Set("Temp:Key", $"Value{i}");
  233. _ = _cfg.Get("Temp:Key");
  234. }
  235. }
  236. /// <summary>
  237. /// 写入后读取不同键(测试写入是否影响其他键的缓存)
  238. /// </summary>
  239. [Benchmark]
  240. [BenchmarkCategory("WriteInvalidation")]
  241. public void WriteAndRead_DifferentKeys()
  242. {
  243. for (int i = 0; i < 100; i++)
  244. {
  245. _cfg.Set($"Temp:Key{i}", $"Value{i}");
  246. _ = _cfg.Get("HotKey");
  247. }
  248. }
  249. /// <summary>
  250. /// 批量写入后批量读取
  251. /// </summary>
  252. [Benchmark]
  253. [BenchmarkCategory("WriteInvalidation")]
  254. public void BatchWrite_ThenBatchRead()
  255. {
  256. // 批量写入
  257. for (int i = 0; i < 100; i++)
  258. {
  259. _cfg.Set($"Batch:Key{i}", $"Value{i}");
  260. }
  261. // 批量读取
  262. for (int i = 0; i < 100; i++)
  263. {
  264. _ = _cfg.Get($"Batch:Key{i}");
  265. }
  266. }
  267. #endregion
  268. #region 首次访问 vs 后续访问
  269. /// <summary>
  270. /// 首次访问新键(冷启动)
  271. /// </summary>
  272. [Benchmark]
  273. [BenchmarkCategory("FirstAccess")]
  274. public string? FirstAccess_NewKey()
  275. {
  276. return _cfg.Get("Data:Key50");
  277. }
  278. /// <summary>
  279. /// 后续访问(预热后)- 通过多次调用模拟
  280. /// </summary>
  281. [Benchmark]
  282. [BenchmarkCategory("FirstAccess")]
  283. public void SubsequentAccess_Warmed()
  284. {
  285. // 预热
  286. _ = _cfg.Get("Data:Key50");
  287. // 后续访问
  288. for (int i = 0; i < 100; i++)
  289. {
  290. _ = _cfg.Get("Data:Key50");
  291. }
  292. }
  293. #endregion
  294. }