ComponentFetchBenchmark.cs 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259
  1. using System;
  2. using System.Collections.Generic;
  3. using BenchmarkDotNet.Attributes;
  4. using Robust.Shared.Analyzers;
  5. using Robust.Shared.Utility;
  6. namespace Content.Benchmarks
  7. {
  8. [SimpleJob]
  9. [Virtual]
  10. public class ComponentFetchBenchmark
  11. {
  12. [Params(5000)] public int NEnt { get; set; }
  13. private readonly Dictionary<(EntityUid, Type), BComponent>
  14. _componentsFlat = new();
  15. private readonly Dictionary<Type, Dictionary<EntityUid, BComponent>> _componentsPart =
  16. new();
  17. private UniqueIndex<Type, BComponent> _allComponents = new();
  18. private readonly List<EntityUid> _lookupEntities = new();
  19. [GlobalSetup]
  20. public void Setup()
  21. {
  22. var random = new Random();
  23. _componentsPart[typeof(BComponent1)] = new Dictionary<EntityUid, BComponent>();
  24. _componentsPart[typeof(BComponent2)] = new Dictionary<EntityUid, BComponent>();
  25. _componentsPart[typeof(BComponent3)] = new Dictionary<EntityUid, BComponent>();
  26. _componentsPart[typeof(BComponent4)] = new Dictionary<EntityUid, BComponent>();
  27. _componentsPart[typeof(BComponentLookup)] = new Dictionary<EntityUid, BComponent>();
  28. _componentsPart[typeof(BComponent6)] = new Dictionary<EntityUid, BComponent>();
  29. _componentsPart[typeof(BComponent7)] = new Dictionary<EntityUid, BComponent>();
  30. _componentsPart[typeof(BComponent8)] = new Dictionary<EntityUid, BComponent>();
  31. _componentsPart[typeof(BComponent9)] = new Dictionary<EntityUid, BComponent>();
  32. for (var i = 0u; i < NEnt; i++)
  33. {
  34. var eId = new EntityUid(i);
  35. if (random.Next(1) == 0)
  36. {
  37. _lookupEntities.Add(eId);
  38. }
  39. var comps = new List<BComponent>
  40. {
  41. new BComponent1(),
  42. new BComponent2(),
  43. new BComponent3(),
  44. new BComponent4(),
  45. new BComponent6(),
  46. new BComponent7(),
  47. new BComponent8(),
  48. new BComponent9(),
  49. };
  50. if (random.Next(1000) == 0)
  51. {
  52. comps.Add(new BComponentLookup());
  53. }
  54. foreach (var comp in comps)
  55. {
  56. comp.Uid = eId;
  57. var type = comp.GetType();
  58. _componentsPart[type][eId] = comp;
  59. _componentsFlat[(eId, type)] = comp;
  60. _allComponents.Add(type, comp);
  61. }
  62. }
  63. }
  64. // These two benchmarks are find "needles in haystack" components.
  65. // We try to look up a component that 0.1% of entities have on 1% of entities.
  66. // Examples of this in the engine are VisibilityComponent lookups during PVS.
  67. [Benchmark]
  68. public void FindPart()
  69. {
  70. foreach (var entityUid in _lookupEntities)
  71. {
  72. var d = _componentsPart[typeof(BComponentLookup)];
  73. d.TryGetValue(entityUid, out _);
  74. }
  75. }
  76. [Benchmark]
  77. public void FindFlat()
  78. {
  79. foreach (var entityUid in _lookupEntities)
  80. {
  81. _componentsFlat.TryGetValue((entityUid, typeof(BComponentLookup)), out _);
  82. }
  83. }
  84. // Iteration benchmarks:
  85. // We try to iterate every instance of a single component (BComponent1) and see which is faster.
  86. [Benchmark]
  87. public void IterPart()
  88. {
  89. var list = _componentsPart[typeof(BComponent1)];
  90. var arr = new BComponent[list.Count];
  91. var i = 0;
  92. foreach (var c in list.Values)
  93. {
  94. arr[i++] = c;
  95. }
  96. }
  97. [Benchmark]
  98. public void IterFlat()
  99. {
  100. var list = _allComponents[typeof(BComponent1)];
  101. var arr = new BComponent[list.Count];
  102. var i = 0;
  103. foreach (var c in list)
  104. {
  105. arr[i++] = c;
  106. }
  107. }
  108. // We do the same as the iteration benchmarks but re-fetch the component every iteration.
  109. // This is what entity systems mostly do via entity queries because crappy code.
  110. [Benchmark]
  111. public void IterFetchPart()
  112. {
  113. var list = _componentsPart[typeof(BComponent1)];
  114. var arr = new BComponent[list.Count];
  115. var i = 0;
  116. foreach (var c in list.Values)
  117. {
  118. var eId = c.Uid;
  119. var d = _componentsPart[typeof(BComponent1)];
  120. arr[i++] = d[eId];
  121. }
  122. }
  123. [Benchmark]
  124. public void IterFetchFlat()
  125. {
  126. var list = _allComponents[typeof(BComponent1)];
  127. var arr = new BComponent[list.Count];
  128. var i = 0;
  129. foreach (var c in list)
  130. {
  131. var eId = c.Uid;
  132. arr[i++] = _componentsFlat[(eId, typeof(BComponent1))];
  133. }
  134. }
  135. // Same as the previous benchmarks but with BComponentLookup instead.
  136. // Which is only on 1% of entities.
  137. [Benchmark]
  138. public void IterFetchPartRare()
  139. {
  140. var list = _componentsPart[typeof(BComponentLookup)];
  141. var arr = new BComponent[list.Count];
  142. var i = 0;
  143. foreach (var c in list.Values)
  144. {
  145. var eId = c.Uid;
  146. var d = _componentsPart[typeof(BComponentLookup)];
  147. arr[i++] = d[eId];
  148. }
  149. }
  150. [Benchmark]
  151. public void IterFetchFlatRare()
  152. {
  153. var list = _allComponents[typeof(BComponentLookup)];
  154. var arr = new BComponent[list.Count];
  155. var i = 0;
  156. foreach (var c in list)
  157. {
  158. var eId = c.Uid;
  159. arr[i++] = _componentsFlat[(eId, typeof(BComponentLookup))];
  160. }
  161. }
  162. private readonly struct EntityUid : IEquatable<EntityUid>
  163. {
  164. public readonly uint Value;
  165. public EntityUid(uint value)
  166. {
  167. Value = value;
  168. }
  169. public bool Equals(EntityUid other)
  170. {
  171. return Value == other.Value;
  172. }
  173. public override bool Equals(object obj)
  174. {
  175. return obj is EntityUid other && Equals(other);
  176. }
  177. public override int GetHashCode()
  178. {
  179. return (int) Value;
  180. }
  181. public static bool operator ==(EntityUid left, EntityUid right)
  182. {
  183. return left.Equals(right);
  184. }
  185. public static bool operator !=(EntityUid left, EntityUid right)
  186. {
  187. return !left.Equals(right);
  188. }
  189. }
  190. private abstract class BComponent
  191. {
  192. public EntityUid Uid;
  193. }
  194. private sealed class BComponent1 : BComponent
  195. {
  196. }
  197. private sealed class BComponent2 : BComponent
  198. {
  199. }
  200. private sealed class BComponent3 : BComponent
  201. {
  202. }
  203. private sealed class BComponent4 : BComponent
  204. {
  205. }
  206. private sealed class BComponentLookup : BComponent
  207. {
  208. }
  209. private sealed class BComponent6 : BComponent
  210. {
  211. }
  212. private sealed class BComponent7 : BComponent
  213. {
  214. }
  215. private sealed class BComponent8 : BComponent
  216. {
  217. }
  218. private sealed class BComponent9 : BComponent
  219. {
  220. }
  221. }
  222. }