ConcurrencyBenchmarks.cs 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375
  1. using BenchmarkDotNet.Attributes;
  2. using Apq.Cfg.Ini;
  3. using Apq.Cfg.Xml;
  4. using Apq.Cfg.Yaml;
  5. using Apq.Cfg.Toml;
  6. namespace Apq.Cfg.Benchmarks;
  7. /// <summary>
  8. /// 并发访问性能基准测试
  9. /// 测试多线程同时读写配置的性能
  10. /// </summary>
  11. [Config(typeof(BenchmarkConfig))]
  12. public class ConcurrencyBenchmarks : IDisposable
  13. {
  14. private readonly string _testDir;
  15. private ICfgRoot _cfg = null!;
  16. private string _configPath = null!;
  17. [Params(4)]
  18. public int ThreadCount { get; set; }
  19. [Params("Json")]
  20. public string SourceType { get; set; } = "Json";
  21. public ConcurrencyBenchmarks()
  22. {
  23. _testDir = Path.Combine(Path.GetTempPath(), $"ApqCfgBench_{Guid.NewGuid():N}");
  24. Directory.CreateDirectory(_testDir);
  25. }
  26. [GlobalSetup]
  27. public void Setup()
  28. {
  29. _configPath = Path.Combine(_testDir, $"config.{SourceType.ToLower()}");
  30. switch (SourceType)
  31. {
  32. case "Json":
  33. File.WriteAllText(_configPath, """
  34. {
  35. "Database": {
  36. "Host": "localhost",
  37. "Port": 5432,
  38. "Name": "testdb"
  39. },
  40. "App": {
  41. "Name": "BenchmarkApp",
  42. "Version": "1.0.0"
  43. },
  44. "Settings": {
  45. "Key1": "Value1",
  46. "Key2": "Value2",
  47. "Key3": "Value3",
  48. "Key4": "Value4",
  49. "Key5": "Value5",
  50. "Key6": "Value6",
  51. "Key7": "Value7",
  52. "Key8": "Value8",
  53. "Key9": "Value9",
  54. "Key10": "Value10",
  55. "Key11": "Value11",
  56. "Key12": "Value12",
  57. "Key13": "Value13",
  58. "Key14": "Value14",
  59. "Key15": "Value15",
  60. "Key16": "Value16"
  61. }
  62. }
  63. """);
  64. _cfg = new CfgBuilder()
  65. .AddJson(_configPath, level: 0, writeable: true, isPrimaryWriter: true)
  66. .Build();
  67. break;
  68. case "Ini":
  69. File.WriteAllText(_configPath, """
  70. [Database]
  71. Host=localhost
  72. Port=5432
  73. Name=testdb
  74. [App]
  75. Name=BenchmarkApp
  76. Version=1.0.0
  77. [Settings]
  78. Key1=Value1
  79. Key2=Value2
  80. Key3=Value3
  81. Key4=Value4
  82. Key5=Value5
  83. Key6=Value6
  84. Key7=Value7
  85. Key8=Value8
  86. Key9=Value9
  87. Key10=Value10
  88. Key11=Value11
  89. Key12=Value12
  90. Key13=Value13
  91. Key14=Value14
  92. Key15=Value15
  93. Key16=Value16
  94. """);
  95. _cfg = new CfgBuilder()
  96. .AddIni(_configPath, level: 0, writeable: true, isPrimaryWriter: true)
  97. .Build();
  98. break;
  99. case "Xml":
  100. File.WriteAllText(_configPath, """
  101. <?xml version="1.0" encoding="utf-8"?>
  102. <configuration>
  103. <Database>
  104. <Host>localhost</Host>
  105. <Port>5432</Port>
  106. <Name>testdb</Name>
  107. </Database>
  108. <App>
  109. <Name>BenchmarkApp</Name>
  110. <Version>1.0.0</Version>
  111. </App>
  112. <Settings>
  113. <Key1>Value1</Key1>
  114. <Key2>Value2</Key2>
  115. <Key3>Value3</Key3>
  116. <Key4>Value4</Key4>
  117. <Key5>Value5</Key5>
  118. <Key6>Value6</Key6>
  119. <Key7>Value7</Key7>
  120. <Key8>Value8</Key8>
  121. <Key9>Value9</Key9>
  122. <Key10>Value10</Key10>
  123. <Key11>Value11</Key11>
  124. <Key12>Value12</Key12>
  125. <Key13>Value13</Key13>
  126. <Key14>Value14</Key14>
  127. <Key15>Value15</Key15>
  128. <Key16>Value16</Key16>
  129. </Settings>
  130. </configuration>
  131. """);
  132. _cfg = new CfgBuilder()
  133. .AddXml(_configPath, level: 0, writeable: true, isPrimaryWriter: true)
  134. .Build();
  135. break;
  136. case "Yaml":
  137. File.WriteAllText(_configPath, """
  138. Database:
  139. Host: localhost
  140. Port: 5432
  141. Name: testdb
  142. App:
  143. Name: BenchmarkApp
  144. Version: 1.0.0
  145. Settings:
  146. Key1: Value1
  147. Key2: Value2
  148. Key3: Value3
  149. Key4: Value4
  150. Key5: Value5
  151. Key6: Value6
  152. Key7: Value7
  153. Key8: Value8
  154. Key9: Value9
  155. Key10: Value10
  156. Key11: Value11
  157. Key12: Value12
  158. Key13: Value13
  159. Key14: Value14
  160. Key15: Value15
  161. Key16: Value16
  162. """);
  163. _cfg = new CfgBuilder()
  164. .AddYaml(_configPath, level: 0, writeable: true, isPrimaryWriter: true)
  165. .Build();
  166. break;
  167. case "Toml":
  168. File.WriteAllText(_configPath, """
  169. [Database]
  170. Host = "localhost"
  171. Port = 5432
  172. Name = "testdb"
  173. [App]
  174. Name = "BenchmarkApp"
  175. Version = "1.0.0"
  176. [Settings]
  177. Key1 = "Value1"
  178. Key2 = "Value2"
  179. Key3 = "Value3"
  180. Key4 = "Value4"
  181. Key5 = "Value5"
  182. Key6 = "Value6"
  183. Key7 = "Value7"
  184. Key8 = "Value8"
  185. Key9 = "Value9"
  186. Key10 = "Value10"
  187. Key11 = "Value11"
  188. Key12 = "Value12"
  189. Key13 = "Value13"
  190. Key14 = "Value14"
  191. Key15 = "Value15"
  192. Key16 = "Value16"
  193. """);
  194. _cfg = new CfgBuilder()
  195. .AddToml(_configPath, level: 0, writeable: true, isPrimaryWriter: true)
  196. .Build();
  197. break;
  198. }
  199. }
  200. [GlobalCleanup]
  201. public void Cleanup()
  202. {
  203. Dispose();
  204. }
  205. public void Dispose()
  206. {
  207. _cfg?.Dispose();
  208. if (Directory.Exists(_testDir))
  209. {
  210. Directory.Delete(_testDir, true);
  211. }
  212. }
  213. /// <summary>
  214. /// 多线程并发读取同一个键
  215. /// </summary>
  216. [Benchmark(Baseline = true)]
  217. [BenchmarkCategory("ConcurrentRead")]
  218. public void ConcurrentRead_SameKey()
  219. {
  220. var tasks = new Task[ThreadCount];
  221. for (int i = 0; i < ThreadCount; i++)
  222. {
  223. tasks[i] = Task.Run(() =>
  224. {
  225. for (int j = 0; j < 100; j++)
  226. {
  227. _ = _cfg.Get("Database:Host");
  228. }
  229. });
  230. }
  231. Task.WaitAll(tasks);
  232. }
  233. /// <summary>
  234. /// 多线程并发读取不同的键
  235. /// </summary>
  236. [Benchmark]
  237. [BenchmarkCategory("ConcurrentRead")]
  238. public void ConcurrentRead_DifferentKeys()
  239. {
  240. var keys = new[] { "Settings:Key1", "Settings:Key2", "Settings:Key3", "Settings:Key4",
  241. "Settings:Key5", "Settings:Key6", "Settings:Key7", "Settings:Key8",
  242. "Settings:Key9", "Settings:Key10", "Settings:Key11", "Settings:Key12",
  243. "Settings:Key13", "Settings:Key14", "Settings:Key15", "Settings:Key16" };
  244. var tasks = new Task[ThreadCount];
  245. for (int i = 0; i < ThreadCount; i++)
  246. {
  247. var keyIndex = i % keys.Length;
  248. tasks[i] = Task.Run(() =>
  249. {
  250. for (int j = 0; j < 100; j++)
  251. {
  252. _ = _cfg.Get(keys[keyIndex]);
  253. }
  254. });
  255. }
  256. Task.WaitAll(tasks);
  257. }
  258. /// <summary>
  259. /// 多线程并发写入不同的键
  260. /// </summary>
  261. [Benchmark]
  262. [BenchmarkCategory("ConcurrentWrite")]
  263. public void ConcurrentWrite_DifferentKeys()
  264. {
  265. var tasks = new Task[ThreadCount];
  266. for (int i = 0; i < ThreadCount; i++)
  267. {
  268. var threadId = i;
  269. tasks[i] = Task.Run(() =>
  270. {
  271. for (int j = 0; j < 100; j++)
  272. {
  273. _cfg.Set($"Temp:Thread{threadId}:Key{j}", $"Value{j}");
  274. }
  275. });
  276. }
  277. Task.WaitAll(tasks);
  278. }
  279. /// <summary>
  280. /// 多线程混合读写操作
  281. /// </summary>
  282. [Benchmark]
  283. [BenchmarkCategory("ConcurrentMixed")]
  284. public void ConcurrentMixed_ReadWrite()
  285. {
  286. var tasks = new Task[ThreadCount];
  287. for (int i = 0; i < ThreadCount; i++)
  288. {
  289. var threadId = i;
  290. var isReader = i % 2 == 0;
  291. tasks[i] = Task.Run(() =>
  292. {
  293. for (int j = 0; j < 100; j++)
  294. {
  295. if (isReader)
  296. {
  297. _ = _cfg.Get("Database:Host");
  298. _ = _cfg.Exists("App:Name");
  299. }
  300. else
  301. {
  302. _cfg.Set($"Temp:Thread{threadId}:Key{j}", $"Value{j}");
  303. }
  304. }
  305. });
  306. }
  307. Task.WaitAll(tasks);
  308. }
  309. /// <summary>
  310. /// 多线程并发检查键存在
  311. /// </summary>
  312. [Benchmark]
  313. [BenchmarkCategory("ConcurrentExists")]
  314. public void ConcurrentExists()
  315. {
  316. var tasks = new Task[ThreadCount];
  317. for (int i = 0; i < ThreadCount; i++)
  318. {
  319. tasks[i] = Task.Run(() =>
  320. {
  321. for (int j = 0; j < 100; j++)
  322. {
  323. _ = _cfg.Exists("Database:Host");
  324. _ = _cfg.Exists("NonExistent:Key");
  325. }
  326. });
  327. }
  328. Task.WaitAll(tasks);
  329. }
  330. /// <summary>
  331. /// 多线程并发写入同一个键(竞争场景)
  332. /// </summary>
  333. [Benchmark]
  334. [BenchmarkCategory("ConcurrentWrite")]
  335. public void ConcurrentWrite_SameKey()
  336. {
  337. var tasks = new Task[ThreadCount];
  338. for (int i = 0; i < ThreadCount; i++)
  339. {
  340. var threadId = i;
  341. tasks[i] = Task.Run(() =>
  342. {
  343. for (int j = 0; j < 100; j++)
  344. {
  345. _cfg.Set("Temp:SharedKey", $"Thread{threadId}_Value{j}");
  346. }
  347. });
  348. }
  349. Task.WaitAll(tasks);
  350. }
  351. }