A game about forced loneliness, made by TACStudios
1using NUnit.Framework;
2using UnityEngine;
3using Unity.Collections.LowLevel.Unsafe;
4using Unity.PerformanceTesting;
5using Unity.PerformanceTesting.Benchmark;
6using System.Runtime.CompilerServices;
7using System.Threading;
8using System.Collections.Generic;
9
10namespace Unity.Collections.PerformanceTests
11{
12 static class HashSetUtil
13 {
14 static public void AllocInt(ref NativeHashSet<int> container, int capacity, bool addValues)
15 {
16 if (capacity >= 0)
17 {
18 Random.InitState(0);
19 container = new NativeHashSet<int>(capacity, Allocator.Persistent);
20 if (addValues)
21 {
22 for (int i = 0; i < capacity; i++)
23 container.Add(i);
24 }
25 }
26 else
27 container.Dispose();
28 }
29 static public void AllocInt(ref NativeHashSet<int> containerA, ref NativeHashSet<int> containerB, int capacity, bool addValues)
30 {
31 AllocInt(ref containerA, capacity, false);
32 AllocInt(ref containerB, capacity, false);
33 if (!addValues)
34 return;
35 for (int i = 0; i < capacity; i++)
36 {
37 containerA.Add(Random.Range(0, capacity * 2));
38 containerB.Add(Random.Range(0, capacity * 2));
39 }
40 }
41 static public void AllocInt(ref UnsafeHashSet<int> container, int capacity, bool addValues)
42 {
43 if (capacity >= 0)
44 {
45 Random.InitState(0);
46 container = new UnsafeHashSet<int>(capacity, Allocator.Persistent);
47 if (addValues)
48 {
49 for (int i = 0; i < capacity; i++)
50 container.Add(i);
51 }
52 }
53 else
54 container.Dispose();
55 }
56 static public void AllocInt(ref UnsafeHashSet<int> containerA, ref UnsafeHashSet<int> containerB, int capacity, bool addValues)
57 {
58 AllocInt(ref containerA, capacity, false);
59 AllocInt(ref containerB, capacity, false);
60 if (!addValues)
61 return;
62 for (int i = 0; i < capacity; i++)
63 {
64 containerA.Add(Random.Range(0, capacity * 2));
65 containerB.Add(Random.Range(0, capacity * 2));
66 }
67 }
68 static public object AllocBclContainer(int capacity, bool addValues)
69 {
70 if (capacity < 0)
71 return null;
72
73 Random.InitState(0);
74 var bclContainer = new HashSet<int>(capacity);
75 if (addValues)
76 {
77 for (int i = 0; i < capacity; i++)
78 bclContainer.Add(i);
79 }
80 return bclContainer;
81 }
82 static public object AllocBclContainerTuple(int capacity, bool addValues)
83 {
84 var tuple = new System.Tuple<HashSet<int>, HashSet<int>>(
85 (HashSet<int>)AllocBclContainer(capacity, false),
86 (HashSet<int>)AllocBclContainer(capacity, false));
87 if (addValues)
88 {
89 for (int i = 0; i < capacity; i++)
90 {
91 tuple.Item1.Add(Random.Range(0, capacity * 2));
92 tuple.Item2.Add(Random.Range(0, capacity * 2));
93 }
94 }
95 return tuple;
96 }
97 static public void CreateRandomKeys(int capacity, ref UnsafeList<int> keys)
98 {
99 if (capacity >= 0)
100 {
101 keys = new UnsafeList<int>(capacity, Allocator.Persistent);
102 Random.InitState(0);
103 for (int i = 0; i < capacity; i++)
104 {
105 int randKey = Random.Range(0, capacity);
106 keys.Add(randKey);
107 }
108 }
109 else
110 keys.Dispose();
111 }
112
113 }
114
115 struct HashSetIsEmpty100k : IBenchmarkContainer
116 {
117 const int kIterations = 100_000;
118 NativeHashSet<int> nativeContainer;
119 UnsafeHashSet<int> unsafeContainer;
120
121 public void AllocNativeContainer(int capacity) => HashSetUtil.AllocInt(ref nativeContainer, capacity, true);
122 public void AllocUnsafeContainer(int capacity) => HashSetUtil.AllocInt(ref unsafeContainer, capacity, true);
123 public object AllocBclContainer(int capacity) => HashSetUtil.AllocBclContainer(capacity, true);
124
125 [MethodImpl(MethodImplOptions.NoOptimization)]
126 public void MeasureNativeContainer()
127 {
128 var reader = nativeContainer.AsReadOnly();
129 for (int i = 0; i < kIterations; i++)
130 _ = reader.IsEmpty;
131 }
132 [MethodImpl(MethodImplOptions.NoOptimization)]
133 public void MeasureUnsafeContainer()
134 {
135 var reader = unsafeContainer.AsReadOnly();
136 for (int i = 0; i < kIterations; i++)
137 _ = reader.IsEmpty;
138 }
139 [MethodImpl(MethodImplOptions.NoOptimization)]
140 public void MeasureBclContainer(object container)
141 {
142 var bclContainer = (HashSet<int>)container;
143 for (int i = 0; i < kIterations; i++)
144 _ = bclContainer.Count == 0;
145 }
146 }
147
148 struct HashSetCount100k : IBenchmarkContainer
149 {
150 const int kIterations = 100_000;
151 NativeHashSet<int> nativeContainer;
152 UnsafeHashSet<int> unsafeContainer;
153
154 public void AllocNativeContainer(int capacity) => HashSetUtil.AllocInt(ref nativeContainer, capacity, true);
155 public void AllocUnsafeContainer(int capacity) => HashSetUtil.AllocInt(ref unsafeContainer, capacity, true);
156 public object AllocBclContainer(int capacity) => HashSetUtil.AllocBclContainer(capacity, true);
157
158 [MethodImpl(MethodImplOptions.NoOptimization)]
159 public void MeasureNativeContainer()
160 {
161 var reader = nativeContainer.AsReadOnly();
162 for (int i = 0; i < kIterations; i++)
163 _ = reader.Count;
164 }
165 [MethodImpl(MethodImplOptions.NoOptimization)]
166 public void MeasureUnsafeContainer()
167 {
168 var reader = unsafeContainer.AsReadOnly();
169 for (int i = 0; i < kIterations; i++)
170 _ = reader.Count;
171 }
172 [MethodImpl(MethodImplOptions.NoOptimization)]
173 public void MeasureBclContainer(object container)
174 {
175 var bclContainer = (HashSet<int>)container;
176 for (int i = 0; i < kIterations; i++)
177 _ = bclContainer.Count;
178 }
179 }
180
181 struct HashSetToNativeArray : IBenchmarkContainer
182 {
183 NativeHashSet<int> nativeContainer;
184 UnsafeHashSet<int> unsafeContainer;
185
186 public void AllocNativeContainer(int capacity) => HashSetUtil.AllocInt(ref nativeContainer, capacity, true);
187 public void AllocUnsafeContainer(int capacity) => HashSetUtil.AllocInt(ref unsafeContainer, capacity, true);
188 public object AllocBclContainer(int capacity) => HashSetUtil.AllocBclContainer(capacity, true);
189
190 public void MeasureNativeContainer()
191 {
192 var asArray = nativeContainer.ToNativeArray(Allocator.Temp);
193 asArray.Dispose();
194 }
195 public void MeasureUnsafeContainer()
196 {
197 var asArray = unsafeContainer.ToNativeArray(Allocator.Temp);
198 asArray.Dispose();
199 }
200 public void MeasureBclContainer(object container)
201 {
202 var bclContainer = (HashSet<int>)container;
203 int[] asArray = new int[bclContainer.Count];
204 bclContainer.CopyTo(asArray, 0);
205 }
206 }
207
208 struct HashSetInsert : IBenchmarkContainer
209 {
210 int capacity;
211 NativeHashSet<int> nativeContainer;
212 UnsafeHashSet<int> unsafeContainer;
213
214 void IBenchmarkContainer.SetParams(int capacity, params int[] args) => this.capacity = capacity;
215
216 public void AllocNativeContainer(int capacity) => HashSetUtil.AllocInt(ref nativeContainer, capacity, false);
217 public void AllocUnsafeContainer(int capacity) => HashSetUtil.AllocInt(ref unsafeContainer, capacity, false);
218 public object AllocBclContainer(int capacity) => HashSetUtil.AllocBclContainer(capacity, false);
219
220 public void MeasureNativeContainer()
221 {
222 for (int i = 0; i < capacity; i++)
223 nativeContainer.Add(i);
224 }
225 public void MeasureUnsafeContainer()
226 {
227 for (int i = 0; i < capacity; i++)
228 unsafeContainer.Add(i);
229 }
230 public void MeasureBclContainer(object container)
231 {
232 var bclContainer = (HashSet<int>)container;
233 for (int i = 0; i < capacity; i++)
234 bclContainer.Add(i);
235 }
236 }
237
238 struct HashSetAddGrow : IBenchmarkContainer
239 {
240 int capacity;
241 int toAdd;
242 NativeHashSet<int> nativeContainer;
243 UnsafeHashSet<int> unsafeContainer;
244
245 void IBenchmarkContainer.SetParams(int capacity, params int[] args)
246 {
247 this.capacity = capacity;
248 toAdd = args[0];
249 }
250
251 public void AllocNativeContainer(int capacity) => HashSetUtil.AllocInt(ref nativeContainer, capacity, true);
252 public void AllocUnsafeContainer(int capacity) => HashSetUtil.AllocInt(ref unsafeContainer, capacity, true);
253 public object AllocBclContainer(int capacity) => HashSetUtil.AllocBclContainer(capacity, true);
254
255 public void MeasureNativeContainer()
256 {
257 // Intentionally setting capacity small and growing by adding more items
258 for (int i = capacity; i < capacity + toAdd; i++)
259 nativeContainer.Add(i);
260 }
261 public void MeasureUnsafeContainer()
262 {
263 // Intentionally setting capacity small and growing by adding more items
264 for (int i = capacity; i < capacity + toAdd; i++)
265 unsafeContainer.Add(i);
266 }
267 public void MeasureBclContainer(object container)
268 {
269 var bclContainer = (HashSet<int>)container;
270 // Intentionally setting capacity small and growing by adding more items
271 for (int i = capacity; i < capacity + toAdd; i++)
272 bclContainer.Add(i);
273 }
274 }
275
276 struct HashSetContains : IBenchmarkContainer
277 {
278 int capacity;
279 NativeHashSet<int> nativeContainer;
280 UnsafeHashSet<int> unsafeContainer;
281 UnsafeList<int> keys;
282
283 void IBenchmarkContainer.SetParams(int capacity, params int[] args) => this.capacity = capacity;
284
285 public void AllocNativeContainer(int capacity)
286 {
287 HashSetUtil.AllocInt(ref nativeContainer, capacity, false);
288 HashSetUtil.CreateRandomKeys(capacity, ref keys);
289 for (int i = 0; i < capacity; i++)
290 nativeContainer.Add(keys[i]);
291 }
292 public void AllocUnsafeContainer(int capacity)
293 {
294 HashSetUtil.AllocInt(ref unsafeContainer, capacity, false);
295 HashSetUtil.CreateRandomKeys(capacity, ref keys);
296 for (int i = 0; i < capacity; i++)
297 unsafeContainer.Add(keys[i]);
298 }
299 public object AllocBclContainer(int capacity)
300 {
301 object container = HashSetUtil.AllocBclContainer(capacity, false);
302 var bclContainer = (HashSet<int>)container;
303 HashSetUtil.CreateRandomKeys(capacity, ref keys);
304 for (int i = 0; i < capacity; i++)
305 bclContainer.Add(keys[i]);
306 return container;
307 }
308
309 public void MeasureNativeContainer()
310 {
311 var reader = nativeContainer.AsReadOnly();
312 bool data = false;
313 for (int i = 0; i < capacity; i++)
314 Volatile.Write(ref data, reader.Contains(keys[i]));
315 }
316 public void MeasureUnsafeContainer()
317 {
318 var reader = unsafeContainer.AsReadOnly();
319 bool data = false;
320 for (int i = 0; i < capacity; i++)
321 Volatile.Write(ref data, reader.Contains(keys[i]));
322 }
323 public void MeasureBclContainer(object container)
324 {
325 var bclContainer = (HashSet<int>)container;
326 bool data = false;
327 for (int i = 0; i < capacity; i++)
328 Volatile.Write(ref data, bclContainer.Contains(keys[i]));
329 }
330 }
331
332 struct HashSetRemove : IBenchmarkContainer
333 {
334 NativeHashSet<int> nativeContainer;
335 UnsafeHashSet<int> unsafeContainer;
336 UnsafeList<int> keys;
337
338 public void AllocNativeContainer(int capacity)
339 {
340 HashSetUtil.AllocInt(ref nativeContainer, capacity, false);
341 HashSetUtil.CreateRandomKeys(capacity, ref keys);
342 for (int i = 0; i < capacity; i++)
343 nativeContainer.Add(keys[i]);
344 }
345 public void AllocUnsafeContainer(int capacity)
346 {
347 HashSetUtil.AllocInt(ref unsafeContainer, capacity, false);
348 HashSetUtil.CreateRandomKeys(capacity, ref keys);
349 for (int i = 0; i < capacity; i++)
350 unsafeContainer.Add(keys[i]);
351 }
352 public object AllocBclContainer(int capacity)
353 {
354 object container = HashSetUtil.AllocBclContainer(capacity, false);
355 var bclContainer = (HashSet<int>)container;
356 HashSetUtil.CreateRandomKeys(capacity, ref keys);
357 for (int i = 0; i < capacity; i++)
358 bclContainer.Add(keys[i]);
359 return container;
360 }
361
362 public void MeasureNativeContainer()
363 {
364 int insertions = keys.Length;
365 for (int i = 0; i < insertions; i++)
366 nativeContainer.Remove(keys[i]);
367 }
368 public void MeasureUnsafeContainer()
369 {
370 int insertions = keys.Length;
371 for (int i = 0; i < insertions; i++)
372 unsafeContainer.Remove(keys[i]);
373 }
374 public void MeasureBclContainer(object container)
375 {
376 var bclContainer = (HashSet<int>)container;
377 int insertions = keys.Length;
378 for (int i = 0; i < insertions; i++)
379 bclContainer.Remove(keys[i]);
380 }
381 }
382
383 struct HashSetForEach : IBenchmarkContainer
384 {
385 NativeHashSet<int> nativeContainer;
386 UnsafeHashSet<int> unsafeContainer;
387 public int total;
388
389 public void AllocNativeContainer(int capacity) => HashSetUtil.AllocInt(ref nativeContainer, capacity, true);
390 public void AllocUnsafeContainer(int capacity) => HashSetUtil.AllocInt(ref unsafeContainer, capacity, true);
391 public object AllocBclContainer(int capacity) => HashSetUtil.AllocBclContainer(capacity, true);
392
393 public void MeasureNativeContainer()
394 {
395 int keep = 0;
396 foreach (var value in nativeContainer)
397 Volatile.Write(ref keep, value);
398 }
399 public void MeasureUnsafeContainer()
400 {
401 int keep = 0;
402 foreach (var value in unsafeContainer)
403 Volatile.Write(ref keep, value);
404 }
405 public void MeasureBclContainer(object container)
406 {
407 int keep = 0;
408 var bclContainer = (HashSet<int>)container;
409 foreach (var value in bclContainer)
410 Volatile.Write(ref keep, value);
411 }
412 }
413
414 struct HashSetUnionWith : IBenchmarkContainer
415 {
416 NativeHashSet<int> nativeContainer;
417 NativeHashSet<int> nativeContainerOther;
418 UnsafeHashSet<int> unsafeContainer;
419 UnsafeHashSet<int> unsafeContainerOther;
420 public int total;
421
422 public void AllocNativeContainer(int capacity) => HashSetUtil.AllocInt(ref nativeContainer, ref nativeContainerOther, capacity, true);
423 public void AllocUnsafeContainer(int capacity) => HashSetUtil.AllocInt(ref unsafeContainer, ref unsafeContainerOther, capacity, true);
424 public object AllocBclContainer(int capacity) => HashSetUtil.AllocBclContainerTuple(capacity, true);
425
426 public void MeasureNativeContainer() => nativeContainer.UnionWith(nativeContainerOther);
427 public void MeasureUnsafeContainer() => unsafeContainer.UnionWith(unsafeContainerOther);
428 public void MeasureBclContainer(object container)
429 {
430 var dotnetContainer = (System.Tuple<HashSet<int>, HashSet<int>>)container;
431 dotnetContainer.Item1.UnionWith(dotnetContainer.Item2);
432 }
433 }
434
435 struct HashSetIntersectWith : IBenchmarkContainer
436 {
437 NativeHashSet<int> nativeContainer;
438 NativeHashSet<int> nativeContainerOther;
439 UnsafeHashSet<int> unsafeContainer;
440 UnsafeHashSet<int> unsafeContainerOther;
441 public int total;
442
443 public void AllocNativeContainer(int capacity) => HashSetUtil.AllocInt(ref nativeContainer, ref nativeContainerOther, capacity, true);
444 public void AllocUnsafeContainer(int capacity) => HashSetUtil.AllocInt(ref unsafeContainer, ref unsafeContainerOther, capacity, true);
445 public object AllocBclContainer(int capacity) => HashSetUtil.AllocBclContainerTuple(capacity, true);
446
447 public void MeasureNativeContainer() => nativeContainer.IntersectWith(nativeContainerOther);
448 public void MeasureUnsafeContainer() => unsafeContainer.IntersectWith(unsafeContainerOther);
449 public void MeasureBclContainer(object container)
450 {
451 var dotnetContainer = (System.Tuple<HashSet<int>, HashSet<int>>)container;
452 dotnetContainer.Item1.IntersectWith(dotnetContainer.Item2);
453 }
454 }
455
456 struct HashSetExceptWith : IBenchmarkContainer
457 {
458 NativeHashSet<int> nativeContainer;
459 NativeHashSet<int> nativeContainerOther;
460 UnsafeHashSet<int> unsafeContainer;
461 UnsafeHashSet<int> unsafeContainerOther;
462 public int total;
463
464 public void AllocNativeContainer(int capacity) => HashSetUtil.AllocInt(ref nativeContainer, ref nativeContainerOther, capacity, true);
465 public void AllocUnsafeContainer(int capacity) => HashSetUtil.AllocInt(ref unsafeContainer, ref unsafeContainerOther, capacity, true);
466 public object AllocBclContainer(int capacity) => HashSetUtil.AllocBclContainerTuple(capacity, true);
467
468 public void MeasureNativeContainer() => nativeContainer.ExceptWith(nativeContainerOther);
469 public void MeasureUnsafeContainer() => unsafeContainer.ExceptWith(unsafeContainerOther);
470 public void MeasureBclContainer(object container)
471 {
472 var dotnetContainer = (System.Tuple<HashSet<int>, HashSet<int>>)container;
473 dotnetContainer.Item1.ExceptWith(dotnetContainer.Item2);
474 }
475 }
476
477
478 [Benchmark(typeof(BenchmarkContainerType))]
479 class HashSet
480 {
481#if UNITY_EDITOR
482 [UnityEditor.MenuItem(BenchmarkContainerConfig.kMenuItemIndividual + nameof(HashSet))]
483 static void RunIndividual()
484 => BenchmarkContainerConfig.RunBenchmark(typeof(HashSet));
485#endif
486
487 [Test, Performance]
488 [Category("Performance")]
489 public unsafe void IsEmpty_x_100k(
490 [Values(0, 100)] int capacity,
491 [Values] BenchmarkContainerType type)
492 {
493 BenchmarkContainerRunner<HashSetIsEmpty100k>.Run(capacity, type);
494 }
495
496 [Test, Performance]
497 [Category("Performance")]
498 public unsafe void Count_x_100k(
499 [Values(0, 100)] int capacity,
500 [Values] BenchmarkContainerType type)
501 {
502 BenchmarkContainerRunner<HashSetCount100k>.Run(capacity, type);
503 }
504
505 [Test, Performance]
506 [Category("Performance")]
507 public unsafe void ToNativeArray(
508 [Values(10000, 100000, 1000000)] int capacity,
509 [Values] BenchmarkContainerType type)
510 {
511 BenchmarkContainerRunner<HashSetToNativeArray>.Run(capacity, type);
512 }
513
514 [Test, Performance]
515 [Category("Performance")]
516 public unsafe void Insert(
517 [Values(10000, 100000, 1000000)] int insertions,
518 [Values] BenchmarkContainerType type)
519 {
520 BenchmarkContainerRunner<HashSetInsert>.Run(insertions, type);
521 }
522
523 [Test, Performance]
524 [Category("Performance")]
525 [BenchmarkTestFootnote("Incrementally grows from `capacity` until reaching size of `growTo`")]
526 public unsafe void AddGrow(
527 [Values(4, 65536)] int capacity,
528 [Values(1024 * 1024)] int growTo,
529 [Values] BenchmarkContainerType type)
530 {
531 BenchmarkContainerRunner<HashSetAddGrow>.Run(capacity, type, growTo);
532 }
533
534 [Test, Performance]
535 [Category("Performance")]
536 public unsafe void Contains(
537 [Values(10000, 100000, 1000000)] int insertions,
538 [Values] BenchmarkContainerType type)
539 {
540 BenchmarkContainerRunner<HashSetContains>.Run(insertions, type);
541 }
542
543 [Test, Performance]
544 [Category("Performance")]
545 public unsafe void Remove(
546 [Values(10000, 100000, 1000000)] int insertions,
547 [Values] BenchmarkContainerType type)
548 {
549 BenchmarkContainerRunner<HashSetRemove>.Run(insertions, type);
550 }
551
552 [Test, Performance]
553 [Category("Performance")]
554 public unsafe void Foreach(
555 [Values(10000, 100000, 1000000)] int insertions,
556 [Values] BenchmarkContainerType type)
557 {
558 BenchmarkContainerRunner<HashSetForEach>.Run(insertions, type);
559 }
560
561 [Test, Performance]
562 [Category("Performance")]
563 public unsafe void UnionWith(
564 [Values(10000, 100000, 1000000)] int insertions,
565 [Values] BenchmarkContainerType type)
566 {
567 BenchmarkContainerRunner<HashSetUnionWith>.Run(insertions, type);
568 }
569
570 [Test, Performance]
571 [Category("Performance")]
572 public unsafe void IntersectWith(
573 [Values(10000, 100000, 1000000)] int insertions,
574 [Values] BenchmarkContainerType type)
575 {
576 BenchmarkContainerRunner<HashSetIntersectWith>.Run(insertions, type);
577 }
578
579 [Test, Performance]
580 [Category("Performance")]
581 public unsafe void ExceptWith(
582 [Values(10000, 100000, 1000000)] int insertions,
583 [Values] BenchmarkContainerType type)
584 {
585 BenchmarkContainerRunner<HashSetExceptWith>.Run(insertions, type);
586 }
587 }
588}