UnityGame/Library/PackageCache/com.unity.collections/Unity.Collections.PerformanceTests/ListPerformanceTests.cs
2024-10-27 10:53:47 +03:00

571 lines
20 KiB
C#

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<int> container, int capacity, bool addValues)
{
if (capacity >= 0)
{
Random.InitState(0);
container = new NativeList<int>(capacity, Allocator.Persistent);
if (addValues)
{
for (int i = 0; i < capacity; i++)
container.Add(i);
}
}
else
container.Dispose();
}
static public void AllocInt(ref UnsafeList<int> container, int capacity, bool addValues)
{
if (capacity >= 0)
{
Random.InitState(0);
container = new UnsafeList<int>(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<int>(capacity);
if (addValues)
{
for (int i = 0; i < capacity; i++)
bclContainer.Add(i);
}
return bclContainer;
}
static public void CreateRandomValues(int capacity, ref UnsafeList<int> values)
{
if (capacity >= 0)
{
values = new UnsafeList<int>(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<int> nativeContainer;
UnsafeList<int> 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<int>)container;
for (int i = 0; i < kIterations; i++)
_ = bclContainer.Count == 0;
}
}
struct ListCount100k : IBenchmarkContainer
{
const int kIterations = 100_000;
NativeList<int> nativeContainer;
UnsafeList<int> 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<int>)container;
for (int i = 0; i < kIterations; i++)
_ = bclContainer.Count;
}
}
struct ListToNativeArray : IBenchmarkContainer
{
NativeList<int> 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<int>)container;
int[] asArray = new int[bclContainer.Count];
bclContainer.CopyTo(asArray, 0);
}
}
struct ListAdd : IBenchmarkContainer
{
int capacity;
NativeList<int> nativeContainer;
UnsafeList<int> 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<int>)container;
for (int i = 0; i < capacity; i++)
bclContainer.Add(i);
}
}
struct ListAddGrow : IBenchmarkContainer
{
int capacity;
int toAdd;
NativeList<int> nativeContainer;
UnsafeList<int> 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<int>)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<int> nativeContainer;
UnsafeList<int> unsafeContainer;
UnsafeList<int> 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<int>)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<int>)container;
bool data = false;
for (int i = 0; i < capacity; i++)
Volatile.Write(ref data, bclContainer.Contains(values[i]));
}
}
struct ListIndexedRead : IBenchmarkContainer
{
NativeList<int> nativeContainer;
UnsafeList<int> unsafeContainer;
UnsafeList<int> 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<int>)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<int> nativeContainer;
UnsafeList<int> unsafeContainer;
UnsafeList<int> 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<int>)container;
int insertions = values.Length;
for (int i = 0; i < insertions; i++)
bclContainer[values[i]] = i;
}
}
struct ListRemove : IBenchmarkContainer
{
NativeList<int> nativeContainer;
UnsafeList<int> unsafeContainer;
UnsafeList<int> 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<int>)container;
int insertions = values.Length;
for (int i = 0; i < insertions; i++)
bclContainer.RemoveAt(values[i]);
}
}
struct ListForEach : IBenchmarkContainer
{
NativeList<int> nativeContainer;
UnsafeList<int> 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<int>)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<ListIsEmpty100k>.Run(capacity, type);
}
[Test, Performance]
[Category("Performance")]
public unsafe void Count_x_100k(
[Values(0, 100)] int capacity,
[Values] BenchmarkContainerType type)
{
BenchmarkContainerRunner<ListCount100k>.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<ListToNativeArray>.Run(capacity, type);
}
[Test, Performance]
[Category("Performance")]
public unsafe void Add(
[Values(10000, 100000, 1000000)] int insertions,
[Values] BenchmarkContainerType type)
{
BenchmarkContainerRunner<ListAdd>.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<ListAddGrow>.Run(capacity, type, growTo);
}
[Test, Performance]
[Category("Performance")]
public unsafe void Contains(
[Values(1000, 10000)] int insertions,
[Values] BenchmarkContainerType type)
{
BenchmarkContainerRunner<ListContains>.Run(insertions, type);
}
[Test, Performance]
[Category("Performance")]
public unsafe void IndexedRead(
[Values(10000, 100000, 1000000)] int insertions,
[Values] BenchmarkContainerType type)
{
BenchmarkContainerRunner<ListIndexedRead>.Run(insertions, type);
}
[Test, Performance]
[Category("Performance")]
public unsafe void IndexedWrite(
[Values(10000, 100000, 1000000)] int insertions,
[Values] BenchmarkContainerType type)
{
BenchmarkContainerRunner<ListIndexedWrite>.Run(insertions, type);
}
[Test, Performance]
[Category("Performance")]
public unsafe void Remove(
[Values(1000, 10000)] int insertions,
[Values] BenchmarkContainerType type)
{
BenchmarkContainerRunner<ListRemove>.Run(insertions, type);
}
[Test, Performance]
[Category("Performance")]
public unsafe void Foreach(
[Values(10000, 100000, 1000000)] int insertions,
[Values] BenchmarkContainerType type)
{
BenchmarkContainerRunner<ListForEach>.Run(insertions, type);
}
}
}