using Unity.PerformanceTesting; using Unity.PerformanceTesting.Benchmark; using Unity.Burst; using Unity.Collections.LowLevel.Unsafe; using Unity.Jobs; using System.Runtime.InteropServices; namespace Unity.Collections.PerformanceTests { /// /// Interface to implement container performance tests which will run using . /// Deriving tests from this interface enables both Performance Test Framework and Benchmark Framework to generate and run /// tests for the contexts described by . /// public interface IBenchmarkContainerParallel { /// /// Override this to add extra int arguments to a performance test implementation as fields in the implementing type. These arguments /// are optionally passed in through . /// /// The initial capacity to requested for the container. /// A variable number of extra arguments to passed through to the test implementation public void SetParams(int capacity, params int[] args) { } /// /// Called during setup for each measurement in a sample set with the capacity to allocate to the native container /// when the benchmark type is , , /// or . /// This is also called during teardown for each measurement in a sample set with '-1' to indicate freeing the container. /// /// The capacity to allocate for the managed container. Capacity of 0 will still create a container, /// but it will be empty. A capacity of -1 will dispose the container and free associated allocation(s). public void AllocNativeContainer(int capacity); /// /// Called during setup for each measurement in a sample set with the capacity to allocate to the unsafe container /// when the benchmark type is , , /// or . /// This is also called during teardown for each measurement in a sample set with '-1' to indicate freeing the container. /// /// The capacity to allocate for the managed container. Capacity of 0 will still create a container, /// but it will be empty. A capacity of -1 will dispose the container and free associated allocation(s). public void AllocUnsafeContainer(int capacity); /// /// Called during setup for each measurement in a sample set with the capacity to allocate to the managed container /// when the benchmark type is . /// This is also called during teardown for each measurement in a sample set with '-1' to indicate freeing the container. /// /// The capacity to allocate for the managed container. Capacity of 0 will still create a container, /// but it will be empty. A capacity of -1 will dispose the container and free associated allocation(s). /// A reference to the allocated container when capacity >= 0, and `null` when capacity < 0. public object AllocBclContainer(int capacity); /// /// The code which will be executed during performance measurement. This should usually be general enough to /// work with any native container. /// /// The worker index out of the number of job workers requested for parallel benchmarking /// The job system thread index which must be specified in some cases for a container's ParallelWriter public void MeasureNativeContainer(int worker, int threadIndex); /// /// The code which will be executed during performance measurement. This should usually be general enough to /// work with any unsafe container. /// /// The worker index out of the number of job workers requested for parallel benchmarking /// The job system thread index which must be specified in some cases for a container's ParallelWriter public void MeasureUnsafeContainer(int worker, int threadIndex); /// /// The code which will be executed during performance measurement. This should usually be general enough to /// work with any managed container provided by the Base Class Library (BCL). /// /// A reference to the managed container allocated in /// The worker index out of the number of job workers requested for parallel benchmarking public void MeasureBclContainer(object container, int worker); } /// /// Provides the API for running container based Performance Framework tests and Benchmark Framework measurements. /// This will typically be the sole call from a performance test. See /// for more information. /// /// An implementation conforming to the interface for running container performance tests and benchmarks. [BurstCompile(CompileSynchronously = true)] public static class BenchmarkContainerRunnerParallel where T : unmanaged, IBenchmarkContainerParallel { [BurstCompile(CompileSynchronously = true, DisableSafetyChecks = true)] unsafe struct NativeJobBurstST : IJob { [NativeDisableUnsafePtrRestriction] public T* methods; public void Execute() => methods->MeasureNativeContainer(0, 0); } [BurstCompile(CompileSynchronously = true, DisableSafetyChecks = false)] unsafe struct NativeJobSafetyBurstST : IJob { [NativeDisableUnsafePtrRestriction] public T* methods; public void Execute() => methods->MeasureNativeContainer(0, 0); } [BurstCompile(CompileSynchronously = true, DisableSafetyChecks = true)] unsafe struct UnsafeJobBurstST : IJob { [NativeDisableUnsafePtrRestriction] public T* methods; public void Execute() => methods->MeasureUnsafeContainer(0, 0); } [BurstCompile(CompileSynchronously = true, DisableSafetyChecks = false)] unsafe struct UnsafeJobSafetyBurstST : IJob { [NativeDisableUnsafePtrRestriction] public T* methods; public void Execute() => methods->MeasureUnsafeContainer(0, 0); } unsafe struct NativeJobMT : IJobParallelFor { [NativeSetThreadIndex] int threadIndex; [NativeDisableUnsafePtrRestriction] public T* methods; public void Execute(int index) => methods->MeasureNativeContainer(index, threadIndex); } [BurstCompile(CompileSynchronously = true, DisableSafetyChecks = true)] unsafe struct NativeJobBurstMT : IJobParallelFor { [NativeSetThreadIndex] int threadIndex; [NativeDisableUnsafePtrRestriction] public T* methods; public void Execute(int index) => methods->MeasureNativeContainer(index, threadIndex); } [BurstCompile(CompileSynchronously = true, DisableSafetyChecks = false)] unsafe struct NativeJobSafetyBurstMT : IJobParallelFor { [NativeSetThreadIndex] int threadIndex; [NativeDisableUnsafePtrRestriction] public T* methods; public void Execute(int index) => methods->MeasureNativeContainer(index, threadIndex); } unsafe struct UnsafeJobMT : IJobParallelFor { [NativeSetThreadIndex] int threadIndex; [NativeDisableUnsafePtrRestriction] public T* methods; public void Execute(int index) => methods->MeasureUnsafeContainer(index, threadIndex); } [BurstCompile(CompileSynchronously = true, DisableSafetyChecks = true)] unsafe struct UnsafeJobBurstMT : IJobParallelFor { [NativeSetThreadIndex] int threadIndex; [NativeDisableUnsafePtrRestriction] public T* methods; public void Execute(int index) => methods->MeasureUnsafeContainer(index, threadIndex); } [BurstCompile(CompileSynchronously = true, DisableSafetyChecks = false)] unsafe struct UnsafeJobSafetyBurstMT : IJobParallelFor { [NativeSetThreadIndex] int threadIndex; [NativeDisableUnsafePtrRestriction] public T* methods; public void Execute(int index) => methods->MeasureUnsafeContainer(index, threadIndex); } unsafe struct BclJobMT : IJobParallelFor { [NativeDisableUnsafePtrRestriction] public T* methods; [NativeDisableUnsafePtrRestriction] public GCHandle* gcHandle; public void Execute(int index) => methods->MeasureBclContainer(gcHandle->Target, index); } static unsafe void RunMT(int workers, int capacity, BenchmarkContainerType type, params int[] args) { var methods = new T(); methods.SetParams(capacity, args); switch (type) { case (BenchmarkContainerType)(BenchmarkContainerConfig.BCL): object container = null; GCHandle* gcHandle = default; BenchmarkMeasure.MeasureParallel(typeof(T), BenchmarkContainerConfig.kCountWarmup, BenchmarkContainerConfig.kCountMeasure, () => new BclJobMT { methods = (T*)UnsafeUtility.AddressOf(ref methods), gcHandle = gcHandle }.Schedule(workers, 1).Complete(), () => { container = methods.AllocBclContainer(capacity); gcHandle = (GCHandle*)UnsafeUtility.Malloc(sizeof(GCHandle), 0, Allocator.Persistent); *gcHandle = GCHandle.Alloc(container); }, () => { gcHandle->Free(); UnsafeUtility.Free(gcHandle, Allocator.Persistent); container = methods.AllocBclContainer(-1); }); break; case BenchmarkContainerType.Native: BenchmarkMeasure.MeasureParallel(typeof(T), BenchmarkContainerConfig.kCountWarmup, BenchmarkContainerConfig.kCountMeasure, () => new NativeJobMT { methods = (T*)UnsafeUtility.AddressOf(ref methods) }.Schedule(workers, 1).Complete(), () => methods.AllocNativeContainer(capacity), () => methods.AllocNativeContainer(-1)); break; case BenchmarkContainerType.NativeBurstSafety: BenchmarkMeasure.MeasureParallel(typeof(T), BenchmarkContainerConfig.kCountWarmup, BenchmarkContainerConfig.kCountMeasure, () => new NativeJobSafetyBurstMT { methods = (T*)UnsafeUtility.AddressOf(ref methods) }.Schedule(workers, 1).Complete(), () => methods.AllocNativeContainer(capacity), () => methods.AllocNativeContainer(-1)); break; case BenchmarkContainerType.NativeBurstNoSafety: BenchmarkMeasure.MeasureParallel(typeof(T), BenchmarkContainerConfig.kCountWarmup, BenchmarkContainerConfig.kCountMeasure, () => new NativeJobBurstMT { methods = (T*)UnsafeUtility.AddressOf(ref methods) }.Schedule(workers, 1).Complete(), () => methods.AllocNativeContainer(capacity), () => methods.AllocNativeContainer(-1)); break; case BenchmarkContainerType.Unsafe: BenchmarkMeasure.Measure(typeof(T), BenchmarkContainerConfig.kCountWarmup, BenchmarkContainerConfig.kCountMeasure, () => new UnsafeJobMT { methods = (T*)UnsafeUtility.AddressOf(ref methods) }.Schedule(workers, 1).Complete(), () => methods.AllocUnsafeContainer(capacity), () => methods.AllocUnsafeContainer(-1)); break; case BenchmarkContainerType.UnsafeBurstSafety: BenchmarkMeasure.Measure(typeof(T), BenchmarkContainerConfig.kCountWarmup, BenchmarkContainerConfig.kCountMeasure, () => new UnsafeJobSafetyBurstMT { methods = (T*)UnsafeUtility.AddressOf(ref methods) }.Schedule(workers, 1).Complete(), () => methods.AllocUnsafeContainer(capacity), () => methods.AllocUnsafeContainer(-1)); break; case BenchmarkContainerType.UnsafeBurstNoSafety: BenchmarkMeasure.MeasureParallel(typeof(T), BenchmarkContainerConfig.kCountWarmup, BenchmarkContainerConfig.kCountMeasure, () => new UnsafeJobBurstMT { methods = (T*)UnsafeUtility.AddressOf(ref methods) }.Schedule(workers, 1).Complete(), () => methods.AllocUnsafeContainer(capacity), () => methods.AllocUnsafeContainer(-1)); break; } } /// /// Called from a typical performance test method to provide both Performance Framework measurements as well as /// Benchmark Framework measurements. A typical usage is similar to: /// [Test, Performance]
/// [Category("Performance")]
/// public unsafe void ToNativeArray(
/// [Values(100000, 1000000, 10000000)] int capacity,
/// [Values] BenchmarkContainerType type)
/// {
/// BenchmarkContainerRunner<HashSetToNativeArray>.RunST(capacity, type);
/// }
///
/// The capacity for the container(s) which will be passed to setup methods /// The benchmark or performance measurement type to run for containers i.e. etc. /// Optional arguments that can be stored in a test implementation class. /// This will run measurements with or directly called on the main thread. public static unsafe void Run(int capacity, BenchmarkContainerType type, params int[] args) { var methods = new T(); methods.SetParams(capacity, args); switch (type) { case (BenchmarkContainerType)(BenchmarkContainerConfig.BCL): object container = null; BenchmarkMeasure.Measure(typeof(T), BenchmarkContainerConfig.kCountWarmup, BenchmarkContainerConfig.kCountMeasure, () => methods.MeasureBclContainer(container, 0), () => container = methods.AllocBclContainer(capacity), () => container = methods.AllocBclContainer(-1)); break; case BenchmarkContainerType.Native: BenchmarkMeasure.Measure(typeof(T), BenchmarkContainerConfig.kCountWarmup, BenchmarkContainerConfig.kCountMeasure, () => methods.MeasureNativeContainer(0, 0), () => methods.AllocNativeContainer(capacity), () => methods.AllocNativeContainer(-1)); break; case BenchmarkContainerType.NativeBurstSafety: BenchmarkMeasure.Measure(typeof(T), BenchmarkContainerConfig.kCountWarmup, BenchmarkContainerConfig.kCountMeasure, () => new NativeJobSafetyBurstST { methods = (T*)UnsafeUtility.AddressOf(ref methods) }.Run(), () => methods.AllocNativeContainer(capacity), () => methods.AllocNativeContainer(-1)); break; case BenchmarkContainerType.NativeBurstNoSafety: BenchmarkMeasure.Measure(typeof(T), BenchmarkContainerConfig.kCountWarmup, BenchmarkContainerConfig.kCountMeasure, () => new NativeJobBurstST { methods = (T*)UnsafeUtility.AddressOf(ref methods) }.Run(), () => methods.AllocNativeContainer(capacity), () => methods.AllocNativeContainer(-1)); break; case BenchmarkContainerType.Unsafe: BenchmarkMeasure.Measure(typeof(T), BenchmarkContainerConfig.kCountWarmup, BenchmarkContainerConfig.kCountMeasure, () => methods.MeasureUnsafeContainer(0, 0), () => methods.AllocUnsafeContainer(capacity), () => methods.AllocUnsafeContainer(-1)); break; case BenchmarkContainerType.UnsafeBurstSafety: BenchmarkMeasure.Measure(typeof(T), BenchmarkContainerConfig.kCountWarmup, BenchmarkContainerConfig.kCountMeasure, () => new UnsafeJobSafetyBurstST { methods = (T*)UnsafeUtility.AddressOf(ref methods) }.Run(), () => methods.AllocUnsafeContainer(capacity), () => methods.AllocUnsafeContainer(-1)); break; case BenchmarkContainerType.UnsafeBurstNoSafety: BenchmarkMeasure.Measure(typeof(T), BenchmarkContainerConfig.kCountWarmup, BenchmarkContainerConfig.kCountMeasure, () => new UnsafeJobBurstST { methods = (T*)UnsafeUtility.AddressOf(ref methods) }.Run(), () => methods.AllocUnsafeContainer(capacity), () => methods.AllocUnsafeContainer(-1)); break; } } /// /// Called from a typical performance test method to provide both Performance Framework measurements as well as /// Benchmark Framework measurements. A typical usage is similar to: /// [Test, Performance]
/// [Category("Performance")]
/// public unsafe void ToNativeArray(
/// [Values(1, 2, 4, 8)] int workers,
/// [Values(100000, 1000000, 10000000)] int capacity,
/// [Values] BenchmarkContainerType type)
/// {
/// BenchmarkContainerRunner<HashSetToNativeArray>.Run(workers, capacity, type);
/// }
///
/// The number of job workers to run performance tests on. These are duplicated across workers rather than split across workers. /// The capacity for the container(s) which will be passed to setup methods /// The benchmark or performance measurement type to run for containers i.e. etc. /// Optional arguments that can be stored in a test implementation class. /// This will run measurements with or based on the number of workers being 1 or 2+, respectively. public static unsafe void Run(int workers, int capacity, BenchmarkContainerType type, params int[] args) { if (workers == 1) Run(capacity, type, args); else RunMT(workers, capacity, type, args); } } }