BatchOperationBenchmarks.cs 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338
  1. using BenchmarkDotNet.Attributes;
  2. namespace Apq.Cfg.Benchmarks;
  3. /// <summary>
  4. /// 批量操作性能基准测试
  5. /// 测试 GetMany/SetMany 与单次操作的性能对比
  6. /// </summary>
  7. [Config(typeof(BenchmarkConfig))]
  8. public class BatchOperationBenchmarks : IDisposable
  9. {
  10. private readonly string _testDir;
  11. private ICfgRoot _cfg = null!;
  12. // 预定义的键
  13. private readonly string[] _keys10 = new string[10];
  14. private readonly string[] _keys50 = new string[50];
  15. private readonly string[] _keys100 = new string[100];
  16. public BatchOperationBenchmarks()
  17. {
  18. _testDir = Path.Combine(Path.GetTempPath(), $"ApqCfgBench_{Guid.NewGuid():N}");
  19. Directory.CreateDirectory(_testDir);
  20. // 预生成键
  21. for (int i = 0; i < 100; i++)
  22. {
  23. var key = $"Data:Key{i}";
  24. if (i < 10) _keys10[i] = key;
  25. if (i < 50) _keys50[i] = key;
  26. _keys100[i] = key;
  27. }
  28. }
  29. [GlobalSetup]
  30. public void Setup()
  31. {
  32. var jsonPath = Path.Combine(_testDir, "config.json");
  33. // 生成包含 100 个键的配置(使用数字字符串以支持类型转换测试)
  34. var content = "{\n \"Data\": {\n";
  35. for (int i = 0; i < 100; i++)
  36. {
  37. content += $" \"Key{i}\": \"{i}\"";
  38. if (i < 99) content += ",";
  39. content += "\n";
  40. }
  41. content += " }\n}";
  42. File.WriteAllText(jsonPath, content);
  43. _cfg = new CfgBuilder()
  44. .AddJson(jsonPath, level: 0, writeable: true, isPrimaryWriter: true)
  45. .Build();
  46. }
  47. [GlobalCleanup]
  48. public void Cleanup()
  49. {
  50. Dispose();
  51. }
  52. public void Dispose()
  53. {
  54. _cfg?.Dispose();
  55. if (Directory.Exists(_testDir))
  56. {
  57. Directory.Delete(_testDir, true);
  58. }
  59. }
  60. #region GetMany vs 单次 Get 对比
  61. /// <summary>
  62. /// 使用 GetMany 批量获取 10 个键
  63. /// </summary>
  64. [Benchmark(Baseline = true)]
  65. [BenchmarkCategory("GetMany")]
  66. public IReadOnlyDictionary<string, string?> GetMany_10Keys()
  67. {
  68. return _cfg.GetMany(_keys10);
  69. }
  70. /// <summary>
  71. /// 使用单次 Get 循环获取 10 个键
  72. /// </summary>
  73. [Benchmark]
  74. [BenchmarkCategory("GetMany")]
  75. public void Get_Loop_10Keys()
  76. {
  77. foreach (var key in _keys10)
  78. {
  79. _ = _cfg.Get(key);
  80. }
  81. }
  82. /// <summary>
  83. /// 使用 GetMany 批量获取 50 个键
  84. /// </summary>
  85. [Benchmark]
  86. [BenchmarkCategory("GetMany")]
  87. public IReadOnlyDictionary<string, string?> GetMany_50Keys()
  88. {
  89. return _cfg.GetMany(_keys50);
  90. }
  91. /// <summary>
  92. /// 使用单次 Get 循环获取 50 个键
  93. /// </summary>
  94. [Benchmark]
  95. [BenchmarkCategory("GetMany")]
  96. public void Get_Loop_50Keys()
  97. {
  98. foreach (var key in _keys50)
  99. {
  100. _ = _cfg.Get(key);
  101. }
  102. }
  103. /// <summary>
  104. /// 使用 GetMany 批量获取 100 个键
  105. /// </summary>
  106. [Benchmark]
  107. [BenchmarkCategory("GetMany")]
  108. public IReadOnlyDictionary<string, string?> GetMany_100Keys()
  109. {
  110. return _cfg.GetMany(_keys100);
  111. }
  112. /// <summary>
  113. /// 使用单次 Get 循环获取 100 个键
  114. /// </summary>
  115. [Benchmark]
  116. [BenchmarkCategory("GetMany")]
  117. public void Get_Loop_100Keys()
  118. {
  119. foreach (var key in _keys100)
  120. {
  121. _ = _cfg.Get(key);
  122. }
  123. }
  124. #endregion
  125. #region GetMany 回调方式(高性能)
  126. /// <summary>
  127. /// 使用 GetMany 回调方式批量获取 10 个键(零分配)
  128. /// </summary>
  129. [Benchmark]
  130. [BenchmarkCategory("GetManyCallback")]
  131. public void GetMany_Callback_10Keys()
  132. {
  133. _cfg.GetMany(_keys10, (key, value) => { _ = value; });
  134. }
  135. /// <summary>
  136. /// 使用 GetMany 回调方式批量获取 50 个键(零分配)
  137. /// </summary>
  138. [Benchmark]
  139. [BenchmarkCategory("GetManyCallback")]
  140. public void GetMany_Callback_50Keys()
  141. {
  142. _cfg.GetMany(_keys50, (key, value) => { _ = value; });
  143. }
  144. /// <summary>
  145. /// 使用 GetMany 回调方式批量获取 100 个键(零分配)
  146. /// </summary>
  147. [Benchmark]
  148. [BenchmarkCategory("GetManyCallback")]
  149. public void GetMany_Callback_100Keys()
  150. {
  151. _cfg.GetMany(_keys100, (key, value) => { _ = value; });
  152. }
  153. #endregion
  154. #region GetMany<T> 类型转换批量获取
  155. /// <summary>
  156. /// 使用 GetMany<int> 批量获取并转换类型
  157. /// </summary>
  158. [Benchmark]
  159. [BenchmarkCategory("GetManyTyped")]
  160. public void GetMany_Typed_10Keys()
  161. {
  162. _ = _cfg.GetMany<int>(_keys10);
  163. }
  164. /// <summary>
  165. /// 使用单次 Get<int> 循环获取并转换类型
  166. /// </summary>
  167. [Benchmark]
  168. [BenchmarkCategory("GetManyTyped")]
  169. public void Get_Typed_Loop_10Keys()
  170. {
  171. foreach (var key in _keys10)
  172. {
  173. _ = _cfg.Get<int>(key);
  174. }
  175. }
  176. #endregion
  177. #region SetMany vs 单次 Set 对比
  178. /// <summary>
  179. /// 使用 SetMany 批量设置 10 个键
  180. /// </summary>
  181. [Benchmark]
  182. [BenchmarkCategory("SetMany")]
  183. public void SetMany_10Keys()
  184. {
  185. var values = new Dictionary<string, string?>();
  186. for (int i = 0; i < 10; i++)
  187. {
  188. values[$"Batch:Key{i}"] = $"Value{i}";
  189. }
  190. _cfg.SetMany(values);
  191. }
  192. /// <summary>
  193. /// 使用单次 Set 循环设置 10 个键
  194. /// </summary>
  195. [Benchmark]
  196. [BenchmarkCategory("SetMany")]
  197. public void Set_Loop_10Keys()
  198. {
  199. for (int i = 0; i < 10; i++)
  200. {
  201. _cfg.Set($"Loop:Key{i}", $"Value{i}");
  202. }
  203. }
  204. /// <summary>
  205. /// 使用 SetMany 批量设置 50 个键
  206. /// </summary>
  207. [Benchmark]
  208. [BenchmarkCategory("SetMany")]
  209. public void SetMany_50Keys()
  210. {
  211. var values = new Dictionary<string, string?>();
  212. for (int i = 0; i < 50; i++)
  213. {
  214. values[$"Batch50:Key{i}"] = $"Value{i}";
  215. }
  216. _cfg.SetMany(values);
  217. }
  218. /// <summary>
  219. /// 使用单次 Set 循环设置 50 个键
  220. /// </summary>
  221. [Benchmark]
  222. [BenchmarkCategory("SetMany")]
  223. public void Set_Loop_50Keys()
  224. {
  225. for (int i = 0; i < 50; i++)
  226. {
  227. _cfg.Set($"Loop50:Key{i}", $"Value{i}");
  228. }
  229. }
  230. /// <summary>
  231. /// 使用 SetMany 批量设置 100 个键
  232. /// </summary>
  233. [Benchmark]
  234. [BenchmarkCategory("SetMany")]
  235. public void SetMany_100Keys()
  236. {
  237. var values = new Dictionary<string, string?>();
  238. for (int i = 0; i < 100; i++)
  239. {
  240. values[$"Batch100:Key{i}"] = $"Value{i}";
  241. }
  242. _cfg.SetMany(values);
  243. }
  244. /// <summary>
  245. /// 使用单次 Set 循环设置 100 个键
  246. /// </summary>
  247. [Benchmark]
  248. [BenchmarkCategory("SetMany")]
  249. public void Set_Loop_100Keys()
  250. {
  251. for (int i = 0; i < 100; i++)
  252. {
  253. _cfg.Set($"Loop100:Key{i}", $"Value{i}");
  254. }
  255. }
  256. #endregion
  257. #region 混合读写场景
  258. /// <summary>
  259. /// 批量读取后批量写入
  260. /// </summary>
  261. [Benchmark]
  262. [BenchmarkCategory("Mixed")]
  263. public void BatchRead_ThenBatchWrite()
  264. {
  265. // 批量读取
  266. var values = _cfg.GetMany(_keys10);
  267. // 批量写入(修改后的值)
  268. var newValues = new Dictionary<string, string?>();
  269. foreach (var kv in values)
  270. {
  271. newValues[kv.Key + "_copy"] = kv.Value + "_modified";
  272. }
  273. _cfg.SetMany(newValues);
  274. }
  275. /// <summary>
  276. /// 单次读取后单次写入(循环)
  277. /// </summary>
  278. [Benchmark]
  279. [BenchmarkCategory("Mixed")]
  280. public void LoopRead_ThenLoopWrite()
  281. {
  282. // 循环读取
  283. var values = new Dictionary<string, string?>();
  284. foreach (var key in _keys10)
  285. {
  286. values[key] = _cfg.Get(key);
  287. }
  288. // 循环写入
  289. foreach (var kv in values)
  290. {
  291. _cfg.Set(kv.Key + "_copy2", kv.Value + "_modified");
  292. }
  293. }
  294. #endregion
  295. }