325 lines
		
	
	
		
			20 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
		
		
			
		
	
	
			325 lines
		
	
	
		
			20 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
|  | 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 | ||
|  | { | ||
|  |     /// <summary> | ||
|  |     /// Interface to implement container performance tests which will run using <see cref="BenchmarkContainerRunnerParallel{T}.Run))"/>. | ||
|  |     /// Deriving tests from this interface enables both Performance Test Framework and Benchmark Framework to generate and run | ||
|  |     /// tests for the contexts described by <see cref="BenchmarkContainerType"/>. | ||
|  |     /// </summary> | ||
|  |     public interface IBenchmarkContainerParallel | ||
|  |     { | ||
|  |         /// <summary> | ||
|  |         /// 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 <see cref="BenchmarkContainerRunner{T}.Run(int, BenchmarkContainerType, int[])"/>. | ||
|  |         /// </summary> | ||
|  |         /// <param name="capacity">The initial capacity to requested for the container.</param> | ||
|  |         /// <param name="args">A variable number of extra arguments to passed through to the test implementation</param> | ||
|  |         public void SetParams(int capacity, params int[] args) { } | ||
|  | 
 | ||
|  |         /// <summary> | ||
|  |         /// Called during setup for each measurement in a sample set with the capacity to allocate to the native container | ||
|  |         /// when the benchmark type is <see cref="BenchmarkContainerType.Native"/>, <see cref="BenchmarkContainerType.NativeBurstNoSafety"/>, | ||
|  |         /// or <see cref="BenchmarkContainerType.NativeBurstSafety"/>.<para /> | ||
|  |         /// This is also called during teardown for each measurement in a sample set with '-1' to indicate freeing the container. | ||
|  |         /// </summary> | ||
|  |         /// <param name="capacity">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).</param> | ||
|  |         public void AllocNativeContainer(int capacity); | ||
|  | 
 | ||
|  |         /// <summary> | ||
|  |         /// Called during setup for each measurement in a sample set with the capacity to allocate to the unsafe container | ||
|  |         /// when the benchmark type is <see cref="BenchmarkContainerType.Unsafe"/>, <see cref="BenchmarkContainerType.UnsafeBurstNoSafety"/>, | ||
|  |         /// or <see cref="BenchmarkContainerType.UnsafeBurstSafety"/>.<para /> | ||
|  |         /// This is also called during teardown for each measurement in a sample set with '-1' to indicate freeing the container. | ||
|  |         /// </summary> | ||
|  |         /// <param name="capacity">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).</param> | ||
|  |         public void AllocUnsafeContainer(int capacity); | ||
|  | 
 | ||
|  |         /// <summary> | ||
|  |         /// Called during setup for each measurement in a sample set with the capacity to allocate to the managed container | ||
|  |         /// when the benchmark type is <see cref="BenchmarkContainerConfig.BCL"/>.<para /> | ||
|  |         /// This is also called during teardown for each measurement in a sample set with '-1' to indicate freeing the container. | ||
|  |         /// </summary> | ||
|  |         /// <param name="capacity">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).</param> | ||
|  |         /// <returns>A reference to the allocated container when capacity >= 0, and `null` when capacity < 0.</returns> | ||
|  |         public object AllocBclContainer(int capacity); | ||
|  | 
 | ||
|  |         /// <summary> | ||
|  |         /// The code which will be executed during performance measurement. This should usually be general enough to | ||
|  |         /// work with any native container. | ||
|  |         /// </summary> | ||
|  |         /// <param name="worker">The worker index out of the number of job workers requested for parallel benchmarking</param> | ||
|  |         /// <param name="threadIndex">The job system thread index which must be specified in some cases for a container's ParallelWriter</param> | ||
|  |         public void MeasureNativeContainer(int worker, int threadIndex); | ||
|  | 
 | ||
|  |         /// <summary> | ||
|  |         /// The code which will be executed during performance measurement. This should usually be general enough to | ||
|  |         /// work with any unsafe container. | ||
|  |         /// </summary> | ||
|  |         /// <param name="worker">The worker index out of the number of job workers requested for parallel benchmarking</param> | ||
|  |         /// <param name="threadIndex">The job system thread index which must be specified in some cases for a container's ParallelWriter</param> | ||
|  |         public void MeasureUnsafeContainer(int worker, int threadIndex); | ||
|  | 
 | ||
|  |         /// <summary> | ||
|  |         /// 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). | ||
|  |         /// </summary> | ||
|  |         /// <param name="container">A reference to the managed container allocated in <see cref="AllocBclContainer(int)"/></param> | ||
|  |         /// <param name="worker">The worker index out of the number of job workers requested for parallel benchmarking</param> | ||
|  |         public void MeasureBclContainer(object container, int worker); | ||
|  |     } | ||
|  | 
 | ||
|  |     /// <summary> | ||
|  |     /// 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 <see cref="Run(int, BenchmarkContainerType, int[])"/> | ||
|  |     /// for more information. | ||
|  |     /// </summary> | ||
|  |     /// <typeparam name="T">An implementation conforming to the <see cref="IBenchmarkContainer"/> interface for running container performance tests and benchmarks.</typeparam> | ||
|  |     [BurstCompile(CompileSynchronously = true)] | ||
|  |     public static class BenchmarkContainerRunnerParallel<T> 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; | ||
|  |             } | ||
|  |         } | ||
|  | 
 | ||
|  |         /// <summary> | ||
|  |         /// 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: | ||
|  |         /// <c>[Test, Performance]<br /> | ||
|  |         /// [Category("Performance")]<br /> | ||
|  |         /// public unsafe void ToNativeArray(<br /> | ||
|  |         ///     [Values(100000, 1000000, 10000000)] int capacity,<br /> | ||
|  |         ///     [Values] BenchmarkContainerType type)<br /> | ||
|  |         /// {<br /> | ||
|  |         ///     BenchmarkContainerRunner<HashSetToNativeArray>.RunST(capacity, type);<br /> | ||
|  |         /// }</c> | ||
|  |         /// </summary> | ||
|  |         /// <param name="capacity">The capacity for the container(s) which will be passed to setup methods</param> | ||
|  |         /// <param name="type">The benchmark or performance measurement type to run for containers i.e. <see cref="BenchmarkContainerType.Native"/> etc.</param> | ||
|  |         /// <param name="args">Optional arguments that can be stored in a test implementation class.</param> | ||
|  |         /// <remarks>This will run measurements with <see cref="IJob"/> or directly called on the main thread.</remarks> | ||
|  |         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; | ||
|  |             } | ||
|  |         } | ||
|  | 
 | ||
|  |         /// <summary> | ||
|  |         /// 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: | ||
|  |         /// <c>[Test, Performance]<br /> | ||
|  |         /// [Category("Performance")]<br /> | ||
|  |         /// public unsafe void ToNativeArray(<br /> | ||
|  |         ///     [Values(1, 2, 4, 8)] int workers,<br /> | ||
|  |         ///     [Values(100000, 1000000, 10000000)] int capacity,<br /> | ||
|  |         ///     [Values] BenchmarkContainerType type)<br /> | ||
|  |         /// {<br /> | ||
|  |         ///     BenchmarkContainerRunner<HashSetToNativeArray>.Run(workers, capacity, type);<br /> | ||
|  |         /// }</c> | ||
|  |         /// </summary> | ||
|  |         /// <param name="workers">The number of job workers to run performance tests on. These are duplicated across workers rather than split across workers.</param> | ||
|  |         /// <param name="capacity">The capacity for the container(s) which will be passed to setup methods</param> | ||
|  |         /// <param name="type">The benchmark or performance measurement type to run for containers i.e. <see cref="BenchmarkContainerType.Native"/> etc.</param> | ||
|  |         /// <param name="args">Optional arguments that can be stored in a test implementation class.</param> | ||
|  |         /// <remarks>This will run measurements with <see cref="IJob"/> or <see cref="IJobParallelFor"/> based on the number of workers being 1 or 2+, respectively.</remarks> | ||
|  |         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); | ||
|  |         } | ||
|  |     } | ||
|  | } |