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