1
0

ComponentQueryBenchmark.cs 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273
  1. #nullable enable
  2. using System;
  3. using System.Runtime.CompilerServices;
  4. using System.Threading.Tasks;
  5. using BenchmarkDotNet.Attributes;
  6. using BenchmarkDotNet.Configs;
  7. using Content.IntegrationTests;
  8. using Content.IntegrationTests.Pair;
  9. using Content.Shared.Clothing.Components;
  10. using Content.Shared.Doors.Components;
  11. using Content.Shared.Item;
  12. using Robust.Shared;
  13. using Robust.Shared.Analyzers;
  14. using Robust.Shared.EntitySerialization;
  15. using Robust.Shared.EntitySerialization.Systems;
  16. using Robust.Shared.GameObjects;
  17. using Robust.Shared.Map.Components;
  18. using Robust.Shared.Random;
  19. using Robust.Shared.Utility;
  20. namespace Content.Benchmarks;
  21. /// <summary>
  22. /// Benchmarks for comparing the speed of various component fetching/lookup related methods, including directed event
  23. /// subscriptions
  24. /// </summary>
  25. [Virtual]
  26. [GroupBenchmarksBy(BenchmarkLogicalGroupRule.ByCategory)]
  27. [CategoriesColumn]
  28. public class ComponentQueryBenchmark
  29. {
  30. public const string Map = "Maps/atlas.yml";
  31. private TestPair _pair = default!;
  32. private IEntityManager _entMan = default!;
  33. private EntityQuery<ItemComponent> _itemQuery;
  34. private EntityQuery<ClothingComponent> _clothingQuery;
  35. private EntityQuery<MapComponent> _mapQuery;
  36. private EntityUid[] _items = default!;
  37. [GlobalSetup]
  38. public void Setup()
  39. {
  40. ProgramShared.PathOffset = "../../../../";
  41. PoolManager.Startup(typeof(QueryBenchSystem).Assembly);
  42. _pair = PoolManager.GetServerClient().GetAwaiter().GetResult();
  43. _entMan = _pair.Server.ResolveDependency<IEntityManager>();
  44. _itemQuery = _entMan.GetEntityQuery<ItemComponent>();
  45. _clothingQuery = _entMan.GetEntityQuery<ClothingComponent>();
  46. _mapQuery = _entMan.GetEntityQuery<MapComponent>();
  47. _pair.Server.ResolveDependency<IRobustRandom>().SetSeed(42);
  48. _pair.Server.WaitPost(() =>
  49. {
  50. var map = new ResPath(Map);
  51. var opts = DeserializationOptions.Default with {InitializeMaps = true};
  52. if (!_entMan.System<MapLoaderSystem>().TryLoadMap(map, out _, out _, opts))
  53. throw new Exception("Map load failed");
  54. }).GetAwaiter().GetResult();
  55. _items = new EntityUid[_entMan.Count<ItemComponent>()];
  56. var i = 0;
  57. var enumerator = _entMan.AllEntityQueryEnumerator<ItemComponent>();
  58. while (enumerator.MoveNext(out var uid, out _))
  59. {
  60. _items[i++] = uid;
  61. }
  62. }
  63. [GlobalCleanup]
  64. public async Task Cleanup()
  65. {
  66. await _pair.DisposeAsync();
  67. PoolManager.Shutdown();
  68. }
  69. #region TryComp
  70. /// <summary>
  71. /// Baseline TryComp benchmark. When the benchmark was created, around 40% of the items were clothing.
  72. /// </summary>
  73. [Benchmark(Baseline = true)]
  74. [BenchmarkCategory("TryComp")]
  75. public int TryComp()
  76. {
  77. var hashCode = 0;
  78. foreach (var uid in _items)
  79. {
  80. if (_clothingQuery.TryGetComponent(uid, out var clothing))
  81. hashCode = HashCode.Combine(hashCode, clothing.GetHashCode());
  82. }
  83. return hashCode;
  84. }
  85. /// <summary>
  86. /// Variant of <see cref="TryComp"/> that is meant to always fail to get a component.
  87. /// </summary>
  88. [Benchmark]
  89. [BenchmarkCategory("TryComp")]
  90. public int TryCompFail()
  91. {
  92. var hashCode = 0;
  93. foreach (var uid in _items)
  94. {
  95. if (_mapQuery.TryGetComponent(uid, out var map))
  96. hashCode = HashCode.Combine(hashCode, map.GetHashCode());
  97. }
  98. return hashCode;
  99. }
  100. /// <summary>
  101. /// Variant of <see cref="TryComp"/> that is meant to always succeed getting a component.
  102. /// </summary>
  103. [Benchmark]
  104. [BenchmarkCategory("TryComp")]
  105. public int TryCompSucceed()
  106. {
  107. var hashCode = 0;
  108. foreach (var uid in _items)
  109. {
  110. if (_itemQuery.TryGetComponent(uid, out var item))
  111. hashCode = HashCode.Combine(hashCode, item.GetHashCode());
  112. }
  113. return hashCode;
  114. }
  115. /// <summary>
  116. /// Variant of <see cref="TryComp"/> that uses `Resolve()` to try get the component.
  117. /// </summary>
  118. [Benchmark]
  119. [BenchmarkCategory("TryComp")]
  120. public int Resolve()
  121. {
  122. var hashCode = 0;
  123. foreach (var uid in _items)
  124. {
  125. DoResolve(uid, ref hashCode);
  126. }
  127. return hashCode;
  128. }
  129. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  130. public void DoResolve(EntityUid uid, ref int hash, ClothingComponent? clothing = null)
  131. {
  132. if (_clothingQuery.Resolve(uid, ref clothing, false))
  133. hash = HashCode.Combine(hash, clothing.GetHashCode());
  134. }
  135. #endregion
  136. #region Enumeration
  137. [Benchmark]
  138. [BenchmarkCategory("Item Enumerator")]
  139. public int SingleItemEnumerator()
  140. {
  141. var hashCode = 0;
  142. var enumerator = _entMan.AllEntityQueryEnumerator<ItemComponent>();
  143. while (enumerator.MoveNext(out var item))
  144. {
  145. hashCode = HashCode.Combine(hashCode, item.GetHashCode());
  146. }
  147. return hashCode;
  148. }
  149. [Benchmark]
  150. [BenchmarkCategory("Item Enumerator")]
  151. public int DoubleItemEnumerator()
  152. {
  153. var hashCode = 0;
  154. var enumerator = _entMan.AllEntityQueryEnumerator<ClothingComponent, ItemComponent>();
  155. while (enumerator.MoveNext(out _, out var item))
  156. {
  157. hashCode = HashCode.Combine(hashCode, item.GetHashCode());
  158. }
  159. return hashCode;
  160. }
  161. [Benchmark]
  162. [BenchmarkCategory("Item Enumerator")]
  163. public int TripleItemEnumerator()
  164. {
  165. var hashCode = 0;
  166. var enumerator = _entMan.AllEntityQueryEnumerator<ClothingComponent, ItemComponent, TransformComponent>();
  167. while (enumerator.MoveNext(out _, out _, out var xform))
  168. {
  169. hashCode = HashCode.Combine(hashCode, xform.GetHashCode());
  170. }
  171. return hashCode;
  172. }
  173. [Benchmark]
  174. [BenchmarkCategory("Airlock Enumerator")]
  175. public int SingleAirlockEnumerator()
  176. {
  177. var hashCode = 0;
  178. var enumerator = _entMan.AllEntityQueryEnumerator<AirlockComponent>();
  179. while (enumerator.MoveNext(out var airlock))
  180. {
  181. hashCode = HashCode.Combine(hashCode, airlock.GetHashCode());
  182. }
  183. return hashCode;
  184. }
  185. [Benchmark]
  186. [BenchmarkCategory("Airlock Enumerator")]
  187. public int DoubleAirlockEnumerator()
  188. {
  189. var hashCode = 0;
  190. var enumerator = _entMan.AllEntityQueryEnumerator<AirlockComponent, DoorComponent>();
  191. while (enumerator.MoveNext(out _, out var door))
  192. {
  193. hashCode = HashCode.Combine(hashCode, door.GetHashCode());
  194. }
  195. return hashCode;
  196. }
  197. [Benchmark]
  198. [BenchmarkCategory("Airlock Enumerator")]
  199. public int TripleAirlockEnumerator()
  200. {
  201. var hashCode = 0;
  202. var enumerator = _entMan.AllEntityQueryEnumerator<AirlockComponent, DoorComponent, TransformComponent>();
  203. while (enumerator.MoveNext(out _, out _, out var xform))
  204. {
  205. hashCode = HashCode.Combine(hashCode, xform.GetHashCode());
  206. }
  207. return hashCode;
  208. }
  209. #endregion
  210. [Benchmark(Baseline = true)]
  211. [BenchmarkCategory("Events")]
  212. public int StructEvents()
  213. {
  214. var ev = new QueryBenchEvent();
  215. foreach (var uid in _items)
  216. {
  217. _entMan.EventBus.RaiseLocalEvent(uid, ref ev);
  218. }
  219. return ev.HashCode;
  220. }
  221. }
  222. [ByRefEvent]
  223. public struct QueryBenchEvent
  224. {
  225. public int HashCode;
  226. }
  227. public sealed class QueryBenchSystem : EntitySystem
  228. {
  229. public override void Initialize()
  230. {
  231. base.Initialize();
  232. SubscribeLocalEvent<ClothingComponent, QueryBenchEvent>(OnEvent);
  233. }
  234. private void OnEvent(EntityUid uid, ClothingComponent component, ref QueryBenchEvent args)
  235. {
  236. args.HashCode = HashCode.Combine(args.HashCode, component.GetHashCode());
  237. }
  238. }