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}