using NUnit.Framework; using UnityEngine; using Unity.Collections.LowLevel.Unsafe; using Unity.PerformanceTesting; using Unity.PerformanceTesting.Benchmark; using System.Runtime.CompilerServices; using System.Threading; namespace Unity.Collections.PerformanceTests { static class ListUtil { static public void AllocInt(ref NativeList container, int capacity, bool addValues) { if (capacity >= 0) { Random.InitState(0); container = new NativeList(capacity, Allocator.Persistent); if (addValues) { for (int i = 0; i < capacity; i++) container.Add(i); } } else container.Dispose(); } static public void AllocInt(ref UnsafeList container, int capacity, bool addValues) { if (capacity >= 0) { Random.InitState(0); container = new UnsafeList(capacity, Allocator.Persistent); if (addValues) { for (int i = 0; i < capacity; i++) container.Add(i); } } else container.Dispose(); } static public object AllocBclContainer(int capacity, bool addValues) { if (capacity < 0) return null; Random.InitState(0); var bclContainer = new System.Collections.Generic.List(capacity); if (addValues) { for (int i = 0; i < capacity; i++) bclContainer.Add(i); } return bclContainer; } static public void CreateRandomValues(int capacity, ref UnsafeList values) { if (capacity >= 0) { values = new UnsafeList(capacity, Allocator.Persistent); Random.InitState(0); for (int i = 0; i < capacity; i++) { int randKey = Random.Range(0, capacity); values.Add(randKey); } } else values.Dispose(); } } struct ListIsEmpty100k : IBenchmarkContainer { const int kIterations = 100_000; NativeList nativeContainer; UnsafeList unsafeContainer; public void AllocNativeContainer(int capacity) => ListUtil.AllocInt(ref nativeContainer, capacity, true); public void AllocUnsafeContainer(int capacity) => ListUtil.AllocInt(ref unsafeContainer, capacity, true); public object AllocBclContainer(int capacity) => ListUtil.AllocBclContainer(capacity, true); [MethodImpl(MethodImplOptions.NoOptimization)] public void MeasureNativeContainer() { for (int i = 0; i < kIterations; i++) _ = nativeContainer.IsEmpty; } [MethodImpl(MethodImplOptions.NoOptimization)] public void MeasureUnsafeContainer() { for (int i = 0; i < kIterations; i++) _ = unsafeContainer.IsEmpty; } [MethodImpl(MethodImplOptions.NoOptimization)] public void MeasureBclContainer(object container) { var bclContainer = (System.Collections.Generic.List)container; for (int i = 0; i < kIterations; i++) _ = bclContainer.Count == 0; } } struct ListCount100k : IBenchmarkContainer { const int kIterations = 100_000; NativeList nativeContainer; UnsafeList unsafeContainer; public void AllocNativeContainer(int capacity) => ListUtil.AllocInt(ref nativeContainer, capacity, true); public void AllocUnsafeContainer(int capacity) => ListUtil.AllocInt(ref unsafeContainer, capacity, true); public object AllocBclContainer(int capacity) => ListUtil.AllocBclContainer(capacity, true); [MethodImpl(MethodImplOptions.NoOptimization)] public void MeasureNativeContainer() { var reader = nativeContainer.AsReadOnly(); for (int i = 0; i < kIterations; i++) _ = reader.Length; } [MethodImpl(MethodImplOptions.NoOptimization)] public void MeasureUnsafeContainer() { var reader = unsafeContainer.AsReadOnly(); for (int i = 0; i < kIterations; i++) _ = reader.Length; } [MethodImpl(MethodImplOptions.NoOptimization)] public void MeasureBclContainer(object container) { var bclContainer = (System.Collections.Generic.List)container; for (int i = 0; i < kIterations; i++) _ = bclContainer.Count; } } struct ListToNativeArray : IBenchmarkContainer { NativeList nativeContainer; public void AllocNativeContainer(int capacity) => ListUtil.AllocInt(ref nativeContainer, capacity, true); public void AllocUnsafeContainer(int capacity) { } public object AllocBclContainer(int capacity) => ListUtil.AllocBclContainer(capacity, true); public void MeasureNativeContainer() { var asArray = nativeContainer.ToArray(Allocator.Temp); asArray.Dispose(); } public void MeasureUnsafeContainer() { } public void MeasureBclContainer(object container) { var bclContainer = (System.Collections.Generic.List)container; int[] asArray = new int[bclContainer.Count]; bclContainer.CopyTo(asArray, 0); } } struct ListAdd : IBenchmarkContainer { int capacity; NativeList nativeContainer; UnsafeList unsafeContainer; void IBenchmarkContainer.SetParams(int capacity, params int[] args) => this.capacity = capacity; public void AllocNativeContainer(int capacity) => ListUtil.AllocInt(ref nativeContainer, capacity, false); public void AllocUnsafeContainer(int capacity) => ListUtil.AllocInt(ref unsafeContainer, capacity, false); public object AllocBclContainer(int capacity) => ListUtil.AllocBclContainer(capacity, false); public void MeasureNativeContainer() { for (int i = 0; i < capacity; i++) nativeContainer.Add(i); } public void MeasureUnsafeContainer() { for (int i = 0; i < capacity; i++) unsafeContainer.Add(i); } public void MeasureBclContainer(object container) { var bclContainer = (System.Collections.Generic.List)container; for (int i = 0; i < capacity; i++) bclContainer.Add(i); } } struct ListAddGrow : IBenchmarkContainer { int capacity; int toAdd; NativeList nativeContainer; UnsafeList unsafeContainer; void IBenchmarkContainer.SetParams(int capacity, params int[] args) { this.capacity = capacity; toAdd = args[0]; } public void AllocNativeContainer(int capacity) => ListUtil.AllocInt(ref nativeContainer, capacity, true); public void AllocUnsafeContainer(int capacity) => ListUtil.AllocInt(ref unsafeContainer, capacity, true); public object AllocBclContainer(int capacity) => ListUtil.AllocBclContainer(capacity, true); public void MeasureNativeContainer() { // Intentionally setting capacity small and growing by adding more items for (int i = capacity; i < capacity + toAdd; i++) nativeContainer.Add(i); } public void MeasureUnsafeContainer() { // Intentionally setting capacity small and growing by adding more items for (int i = capacity; i < capacity + toAdd; i++) unsafeContainer.Add(i); } public void MeasureBclContainer(object container) { var bclContainer = (System.Collections.Generic.List)container; // Intentionally setting capacity small and growing by adding more items for (int i = capacity; i < capacity + toAdd; i++) bclContainer.Add(i); } } struct ListContains : IBenchmarkContainer { int capacity; NativeList nativeContainer; UnsafeList unsafeContainer; UnsafeList values; void IBenchmarkContainer.SetParams(int capacity, params int[] args) => this.capacity = capacity; public void AllocNativeContainer(int capacity) { ListUtil.AllocInt(ref nativeContainer, capacity, false); ListUtil.CreateRandomValues(capacity, ref values); for (int i = 0; i < capacity; i++) nativeContainer.Add(values[i]); } public void AllocUnsafeContainer(int capacity) { ListUtil.AllocInt(ref unsafeContainer, capacity, false); ListUtil.CreateRandomValues(capacity, ref values); for (int i = 0; i < capacity; i++) unsafeContainer.Add(values[i]); } public object AllocBclContainer(int capacity) { object container = ListUtil.AllocBclContainer(capacity, false); var bclContainer = (System.Collections.Generic.List)container; ListUtil.CreateRandomValues(capacity, ref values); for (int i = 0; i < capacity; i++) bclContainer.Add(values[i]); return container; } public void MeasureNativeContainer() { var reader = nativeContainer.AsReadOnly(); bool data = false; for (int i = 0; i < capacity; i++) Volatile.Write(ref data, reader.Contains(values[i])); } public void MeasureUnsafeContainer() { var reader = unsafeContainer.AsReadOnly(); bool data = false; for (int i = 0; i < capacity; i++) Volatile.Write(ref data, reader.Contains(values[i])); } public void MeasureBclContainer(object container) { var bclContainer = (System.Collections.Generic.List)container; bool data = false; for (int i = 0; i < capacity; i++) Volatile.Write(ref data, bclContainer.Contains(values[i])); } } struct ListIndexedRead : IBenchmarkContainer { NativeList nativeContainer; UnsafeList unsafeContainer; UnsafeList values; public void AllocNativeContainer(int capacity) { ListUtil.AllocInt(ref nativeContainer, capacity, true); ListUtil.CreateRandomValues(capacity, ref values); } public void AllocUnsafeContainer(int capacity) { ListUtil.AllocInt(ref unsafeContainer, capacity, true); ListUtil.CreateRandomValues(capacity, ref values); } public object AllocBclContainer(int capacity) { object container = ListUtil.AllocBclContainer(capacity, true); ListUtil.CreateRandomValues(capacity, ref values); return container; } public void MeasureNativeContainer() { var reader = nativeContainer.AsReadOnly(); int insertions = values.Length; int value = 0; for (int i = 0; i < insertions; i++) Volatile.Write(ref value, reader[values[i]]); } public void MeasureUnsafeContainer() { int insertions = values.Length; int value = 0; for (int i = 0; i < insertions; i++) Volatile.Write(ref value, unsafeContainer[values[i]]); } public void MeasureBclContainer(object container) { var bclContainer = (System.Collections.Generic.List)container; int insertions = values.Length; int value = 0; for (int i = 0; i < insertions; i++) Volatile.Write(ref value, bclContainer[values[i]]); } } struct ListIndexedWrite : IBenchmarkContainer { NativeList nativeContainer; UnsafeList unsafeContainer; UnsafeList values; public void AllocNativeContainer(int capacity) { ListUtil.AllocInt(ref nativeContainer, capacity, true); ListUtil.CreateRandomValues(capacity, ref values); } public void AllocUnsafeContainer(int capacity) { ListUtil.AllocInt(ref unsafeContainer, capacity, true); ListUtil.CreateRandomValues(capacity, ref values); } public object AllocBclContainer(int capacity) { object container = ListUtil.AllocBclContainer(capacity, true); ListUtil.CreateRandomValues(capacity, ref values); return container; } public void MeasureNativeContainer() { int insertions = values.Length; for (int i = 0; i < insertions; i++) nativeContainer[values[i]] = i; } public void MeasureUnsafeContainer() { int insertions = values.Length; for (int i = 0; i < insertions; i++) unsafeContainer[values[i]] = i; } public void MeasureBclContainer(object container) { var bclContainer = (System.Collections.Generic.List)container; int insertions = values.Length; for (int i = 0; i < insertions; i++) bclContainer[values[i]] = i; } } struct ListRemove : IBenchmarkContainer { NativeList nativeContainer; UnsafeList unsafeContainer; UnsafeList values; void FixValues() { // Ensure if we iterate this list and remove a random index, it will always be a valid index given how many elements still remain. int max = values.Length; while (--max >= 0) { int reverseIndex = values.Length - 1 - max; int value = values[reverseIndex]; if (value > max) values[reverseIndex] = max; } } public void AllocNativeContainer(int capacity) { ListUtil.AllocInt(ref nativeContainer, capacity, true); ListUtil.CreateRandomValues(capacity, ref values); FixValues(); } public void AllocUnsafeContainer(int capacity) { ListUtil.AllocInt(ref unsafeContainer, capacity, true); ListUtil.CreateRandomValues(capacity, ref values); FixValues(); } public object AllocBclContainer(int capacity) { object container = ListUtil.AllocBclContainer(capacity, true); ListUtil.CreateRandomValues(capacity, ref values); FixValues(); return container; } public void MeasureNativeContainer() { int insertions = values.Length; for (int i = 0; i < insertions; i++) nativeContainer.RemoveAt(values[i]); } public void MeasureUnsafeContainer() { int insertions = values.Length; for (int i = 0; i < insertions; i++) unsafeContainer.RemoveAt(values[i]); } public void MeasureBclContainer(object container) { var bclContainer = (System.Collections.Generic.List)container; int insertions = values.Length; for (int i = 0; i < insertions; i++) bclContainer.RemoveAt(values[i]); } } struct ListForEach : IBenchmarkContainer { NativeList nativeContainer; UnsafeList unsafeContainer; public int total; public void AllocNativeContainer(int capacity) => ListUtil.AllocInt(ref nativeContainer, capacity, true); public void AllocUnsafeContainer(int capacity) => ListUtil.AllocInt(ref unsafeContainer, capacity, true); public object AllocBclContainer(int capacity) => ListUtil.AllocBclContainer(capacity, true); public void MeasureNativeContainer() { int value = 0; foreach (var element in nativeContainer) Volatile.Write(ref value, element); } public void MeasureUnsafeContainer() { int value = 0; foreach (var element in unsafeContainer) Volatile.Write(ref value, element); } public void MeasureBclContainer(object container) { int value = 0; var bclContainer = (System.Collections.Generic.List)container; foreach (var element in bclContainer) Volatile.Write(ref value, element); } } [Benchmark(typeof(BenchmarkContainerType))] class List { #if UNITY_EDITOR [UnityEditor.MenuItem(BenchmarkContainerConfig.kMenuItemIndividual + nameof(List))] static void RunIndividual() => BenchmarkContainerConfig.RunBenchmark(typeof(List)); #endif [Test, Performance] [Category("Performance")] public unsafe void IsEmpty_x_100k( [Values(0, 100)] int capacity, [Values] BenchmarkContainerType type) { BenchmarkContainerRunner.Run(capacity, type); } [Test, Performance] [Category("Performance")] public unsafe void Count_x_100k( [Values(0, 100)] int capacity, [Values] BenchmarkContainerType type) { BenchmarkContainerRunner.Run(capacity, type); } [Test, Performance] [Category("Performance")] public unsafe void ToNativeArray( [Values(10000, 100000, 1000000)] int capacity, [Values(BenchmarkContainerType.Native, BenchmarkContainerType.NativeBurstSafety, BenchmarkContainerType.NativeBurstNoSafety)] BenchmarkContainerType type) { BenchmarkContainerRunner.Run(capacity, type); } [Test, Performance] [Category("Performance")] public unsafe void Add( [Values(10000, 100000, 1000000)] int insertions, [Values] BenchmarkContainerType type) { BenchmarkContainerRunner.Run(insertions, type); } [Test, Performance] [Category("Performance")] [BenchmarkTestFootnote("Incrementally grows from `capacity` until reaching size of `growTo`")] public unsafe void AddGrow( [Values(4, 65536)] int capacity, [Values(1024 * 1024)] int growTo, [Values] BenchmarkContainerType type) { BenchmarkContainerRunner.Run(capacity, type, growTo); } [Test, Performance] [Category("Performance")] public unsafe void Contains( [Values(1000, 10000)] int insertions, [Values] BenchmarkContainerType type) { BenchmarkContainerRunner.Run(insertions, type); } [Test, Performance] [Category("Performance")] public unsafe void IndexedRead( [Values(10000, 100000, 1000000)] int insertions, [Values] BenchmarkContainerType type) { BenchmarkContainerRunner.Run(insertions, type); } [Test, Performance] [Category("Performance")] public unsafe void IndexedWrite( [Values(10000, 100000, 1000000)] int insertions, [Values] BenchmarkContainerType type) { BenchmarkContainerRunner.Run(insertions, type); } [Test, Performance] [Category("Performance")] public unsafe void Remove( [Values(1000, 10000)] int insertions, [Values] BenchmarkContainerType type) { BenchmarkContainerRunner.Run(insertions, type); } [Test, Performance] [Category("Performance")] public unsafe void Foreach( [Values(10000, 100000, 1000000)] int insertions, [Values] BenchmarkContainerType type) { BenchmarkContainerRunner.Run(insertions, type); } } }