A game about forced loneliness, made by TACStudios
at master 324 lines 20 kB view raw
1using Unity.PerformanceTesting; 2using Unity.PerformanceTesting.Benchmark; 3using Unity.Burst; 4using Unity.Collections.LowLevel.Unsafe; 5using Unity.Jobs; 6using System.Runtime.InteropServices; 7 8namespace Unity.Collections.PerformanceTests 9{ 10 /// <summary> 11 /// Interface to implement container performance tests which will run using <see cref="BenchmarkContainerRunnerParallel{T}.Run))"/>. 12 /// Deriving tests from this interface enables both Performance Test Framework and Benchmark Framework to generate and run 13 /// tests for the contexts described by <see cref="BenchmarkContainerType"/>. 14 /// </summary> 15 public interface IBenchmarkContainerParallel 16 { 17 /// <summary> 18 /// Override this to add extra int arguments to a performance test implementation as fields in the implementing type. These arguments 19 /// are optionally passed in through <see cref="BenchmarkContainerRunner{T}.Run(int, BenchmarkContainerType, int[])"/>. 20 /// </summary> 21 /// <param name="capacity">The initial capacity to requested for the container.</param> 22 /// <param name="args">A variable number of extra arguments to passed through to the test implementation</param> 23 public void SetParams(int capacity, params int[] args) { } 24 25 /// <summary> 26 /// Called during setup for each measurement in a sample set with the capacity to allocate to the native container 27 /// when the benchmark type is <see cref="BenchmarkContainerType.Native"/>, <see cref="BenchmarkContainerType.NativeBurstNoSafety"/>, 28 /// or <see cref="BenchmarkContainerType.NativeBurstSafety"/>.<para /> 29 /// This is also called during teardown for each measurement in a sample set with '-1' to indicate freeing the container. 30 /// </summary> 31 /// <param name="capacity">The capacity to allocate for the managed container. Capacity of 0 will still create a container, 32 /// but it will be empty. A capacity of -1 will dispose the container and free associated allocation(s).</param> 33 public void AllocNativeContainer(int capacity); 34 35 /// <summary> 36 /// Called during setup for each measurement in a sample set with the capacity to allocate to the unsafe container 37 /// when the benchmark type is <see cref="BenchmarkContainerType.Unsafe"/>, <see cref="BenchmarkContainerType.UnsafeBurstNoSafety"/>, 38 /// or <see cref="BenchmarkContainerType.UnsafeBurstSafety"/>.<para /> 39 /// This is also called during teardown for each measurement in a sample set with '-1' to indicate freeing the container. 40 /// </summary> 41 /// <param name="capacity">The capacity to allocate for the managed container. Capacity of 0 will still create a container, 42 /// but it will be empty. A capacity of -1 will dispose the container and free associated allocation(s).</param> 43 public void AllocUnsafeContainer(int capacity); 44 45 /// <summary> 46 /// Called during setup for each measurement in a sample set with the capacity to allocate to the managed container 47 /// when the benchmark type is <see cref="BenchmarkContainerConfig.BCL"/>.<para /> 48 /// This is also called during teardown for each measurement in a sample set with '-1' to indicate freeing the container. 49 /// </summary> 50 /// <param name="capacity">The capacity to allocate for the managed container. Capacity of 0 will still create a container, 51 /// but it will be empty. A capacity of -1 will dispose the container and free associated allocation(s).</param> 52 /// <returns>A reference to the allocated container when capacity &gt;= 0, and `null` when capacity &lt; 0.</returns> 53 public object AllocBclContainer(int capacity); 54 55 /// <summary> 56 /// The code which will be executed during performance measurement. This should usually be general enough to 57 /// work with any native container. 58 /// </summary> 59 /// <param name="worker">The worker index out of the number of job workers requested for parallel benchmarking</param> 60 /// <param name="threadIndex">The job system thread index which must be specified in some cases for a container's ParallelWriter</param> 61 public void MeasureNativeContainer(int worker, int threadIndex); 62 63 /// <summary> 64 /// The code which will be executed during performance measurement. This should usually be general enough to 65 /// work with any unsafe container. 66 /// </summary> 67 /// <param name="worker">The worker index out of the number of job workers requested for parallel benchmarking</param> 68 /// <param name="threadIndex">The job system thread index which must be specified in some cases for a container's ParallelWriter</param> 69 public void MeasureUnsafeContainer(int worker, int threadIndex); 70 71 /// <summary> 72 /// The code which will be executed during performance measurement. This should usually be general enough to 73 /// work with any managed container provided by the Base Class Library (BCL). 74 /// </summary> 75 /// <param name="container">A reference to the managed container allocated in <see cref="AllocBclContainer(int)"/></param> 76 /// <param name="worker">The worker index out of the number of job workers requested for parallel benchmarking</param> 77 public void MeasureBclContainer(object container, int worker); 78 } 79 80 /// <summary> 81 /// Provides the API for running container based Performance Framework tests and Benchmark Framework measurements. 82 /// This will typically be the sole call from a performance test. See <see cref="Run(int, BenchmarkContainerType, int[])"/> 83 /// for more information. 84 /// </summary> 85 /// <typeparam name="T">An implementation conforming to the <see cref="IBenchmarkContainer"/> interface for running container performance tests and benchmarks.</typeparam> 86 [BurstCompile(CompileSynchronously = true)] 87 public static class BenchmarkContainerRunnerParallel<T> where T : unmanaged, IBenchmarkContainerParallel 88 { 89 [BurstCompile(CompileSynchronously = true, DisableSafetyChecks = true)] 90 unsafe struct NativeJobBurstST : IJob 91 { 92 [NativeDisableUnsafePtrRestriction] public T* methods; 93 public void Execute() => methods->MeasureNativeContainer(0, 0); 94 } 95 [BurstCompile(CompileSynchronously = true, DisableSafetyChecks = false)] 96 unsafe struct NativeJobSafetyBurstST : IJob 97 { 98 [NativeDisableUnsafePtrRestriction] public T* methods; 99 public void Execute() => methods->MeasureNativeContainer(0, 0); 100 } 101 102 [BurstCompile(CompileSynchronously = true, DisableSafetyChecks = true)] 103 unsafe struct UnsafeJobBurstST : IJob 104 { 105 [NativeDisableUnsafePtrRestriction] public T* methods; 106 public void Execute() => methods->MeasureUnsafeContainer(0, 0); 107 } 108 [BurstCompile(CompileSynchronously = true, DisableSafetyChecks = false)] 109 unsafe struct UnsafeJobSafetyBurstST : IJob 110 { 111 [NativeDisableUnsafePtrRestriction] public T* methods; 112 public void Execute() => methods->MeasureUnsafeContainer(0, 0); 113 } 114 115 unsafe struct NativeJobMT : IJobParallelFor 116 { 117 [NativeSetThreadIndex] int threadIndex; 118 [NativeDisableUnsafePtrRestriction] public T* methods; 119 public void Execute(int index) => methods->MeasureNativeContainer(index, threadIndex); 120 } 121 [BurstCompile(CompileSynchronously = true, DisableSafetyChecks = true)] 122 unsafe struct NativeJobBurstMT : IJobParallelFor 123 { 124 [NativeSetThreadIndex] int threadIndex; 125 [NativeDisableUnsafePtrRestriction] public T* methods; 126 public void Execute(int index) => methods->MeasureNativeContainer(index, threadIndex); 127 } 128 [BurstCompile(CompileSynchronously = true, DisableSafetyChecks = false)] 129 unsafe struct NativeJobSafetyBurstMT : IJobParallelFor 130 { 131 [NativeSetThreadIndex] int threadIndex; 132 [NativeDisableUnsafePtrRestriction] public T* methods; 133 public void Execute(int index) => methods->MeasureNativeContainer(index, threadIndex); 134 } 135 136 unsafe struct UnsafeJobMT : IJobParallelFor 137 { 138 [NativeSetThreadIndex] int threadIndex; 139 [NativeDisableUnsafePtrRestriction] public T* methods; 140 public void Execute(int index) => methods->MeasureUnsafeContainer(index, threadIndex); 141 } 142 [BurstCompile(CompileSynchronously = true, DisableSafetyChecks = true)] 143 unsafe struct UnsafeJobBurstMT : IJobParallelFor 144 { 145 [NativeSetThreadIndex] int threadIndex; 146 [NativeDisableUnsafePtrRestriction] public T* methods; 147 public void Execute(int index) => methods->MeasureUnsafeContainer(index, threadIndex); 148 } 149 [BurstCompile(CompileSynchronously = true, DisableSafetyChecks = false)] 150 unsafe struct UnsafeJobSafetyBurstMT : IJobParallelFor 151 { 152 [NativeSetThreadIndex] int threadIndex; 153 [NativeDisableUnsafePtrRestriction] public T* methods; 154 public void Execute(int index) => methods->MeasureUnsafeContainer(index, threadIndex); 155 } 156 157 unsafe struct BclJobMT : IJobParallelFor 158 { 159 [NativeDisableUnsafePtrRestriction] public T* methods; 160 [NativeDisableUnsafePtrRestriction] public GCHandle* gcHandle; 161 public void Execute(int index) => methods->MeasureBclContainer(gcHandle->Target, index); 162 } 163 164 static unsafe void RunMT(int workers, int capacity, BenchmarkContainerType type, params int[] args) 165 { 166 var methods = new T(); 167 methods.SetParams(capacity, args); 168 169 switch (type) 170 { 171 case (BenchmarkContainerType)(BenchmarkContainerConfig.BCL): 172 object container = null; 173 GCHandle* gcHandle = default; 174 BenchmarkMeasure.MeasureParallel(typeof(T), 175 BenchmarkContainerConfig.kCountWarmup, BenchmarkContainerConfig.kCountMeasure, 176 () => new BclJobMT { methods = (T*)UnsafeUtility.AddressOf(ref methods), gcHandle = gcHandle }.Schedule(workers, 1).Complete(), 177 () => 178 { 179 container = methods.AllocBclContainer(capacity); 180 gcHandle = (GCHandle*)UnsafeUtility.Malloc(sizeof(GCHandle), 0, Allocator.Persistent); 181 *gcHandle = GCHandle.Alloc(container); 182 }, 183 () => 184 { 185 gcHandle->Free(); 186 UnsafeUtility.Free(gcHandle, Allocator.Persistent); 187 container = methods.AllocBclContainer(-1); 188 }); 189 break; 190 case BenchmarkContainerType.Native: 191 BenchmarkMeasure.MeasureParallel(typeof(T), 192 BenchmarkContainerConfig.kCountWarmup, BenchmarkContainerConfig.kCountMeasure, 193 () => new NativeJobMT { methods = (T*)UnsafeUtility.AddressOf(ref methods) }.Schedule(workers, 1).Complete(), 194 () => methods.AllocNativeContainer(capacity), () => methods.AllocNativeContainer(-1)); 195 break; 196 case BenchmarkContainerType.NativeBurstSafety: 197 BenchmarkMeasure.MeasureParallel(typeof(T), 198 BenchmarkContainerConfig.kCountWarmup, BenchmarkContainerConfig.kCountMeasure, 199 () => new NativeJobSafetyBurstMT { methods = (T*)UnsafeUtility.AddressOf(ref methods) }.Schedule(workers, 1).Complete(), 200 () => methods.AllocNativeContainer(capacity), () => methods.AllocNativeContainer(-1)); 201 break; 202 case BenchmarkContainerType.NativeBurstNoSafety: 203 BenchmarkMeasure.MeasureParallel(typeof(T), 204 BenchmarkContainerConfig.kCountWarmup, BenchmarkContainerConfig.kCountMeasure, 205 () => new NativeJobBurstMT { methods = (T*)UnsafeUtility.AddressOf(ref methods) }.Schedule(workers, 1).Complete(), 206 () => methods.AllocNativeContainer(capacity), () => methods.AllocNativeContainer(-1)); 207 break; 208 case BenchmarkContainerType.Unsafe: 209 BenchmarkMeasure.Measure(typeof(T), 210 BenchmarkContainerConfig.kCountWarmup, BenchmarkContainerConfig.kCountMeasure, 211 () => new UnsafeJobMT { methods = (T*)UnsafeUtility.AddressOf(ref methods) }.Schedule(workers, 1).Complete(), 212 () => methods.AllocUnsafeContainer(capacity), () => methods.AllocUnsafeContainer(-1)); 213 break; 214 case BenchmarkContainerType.UnsafeBurstSafety: 215 BenchmarkMeasure.Measure(typeof(T), 216 BenchmarkContainerConfig.kCountWarmup, BenchmarkContainerConfig.kCountMeasure, 217 () => new UnsafeJobSafetyBurstMT { methods = (T*)UnsafeUtility.AddressOf(ref methods) }.Schedule(workers, 1).Complete(), 218 () => methods.AllocUnsafeContainer(capacity), () => methods.AllocUnsafeContainer(-1)); 219 break; 220 case BenchmarkContainerType.UnsafeBurstNoSafety: 221 BenchmarkMeasure.MeasureParallel(typeof(T), 222 BenchmarkContainerConfig.kCountWarmup, BenchmarkContainerConfig.kCountMeasure, 223 () => new UnsafeJobBurstMT { methods = (T*)UnsafeUtility.AddressOf(ref methods) }.Schedule(workers, 1).Complete(), 224 () => methods.AllocUnsafeContainer(capacity), () => methods.AllocUnsafeContainer(-1)); 225 break; 226 } 227 } 228 229 /// <summary> 230 /// Called from a typical performance test method to provide both Performance Framework measurements as well as 231 /// Benchmark Framework measurements. A typical usage is similar to: 232 /// <c>[Test, Performance]<br /> 233 /// [Category("Performance")]<br /> 234 /// public unsafe void ToNativeArray(<br /> 235 /// [Values(100000, 1000000, 10000000)] int capacity,<br /> 236 /// [Values] BenchmarkContainerType type)<br /> 237 /// {<br /> 238 /// BenchmarkContainerRunner&lt;HashSetToNativeArray&gt;.RunST(capacity, type);<br /> 239 /// }</c> 240 /// </summary> 241 /// <param name="capacity">The capacity for the container(s) which will be passed to setup methods</param> 242 /// <param name="type">The benchmark or performance measurement type to run for containers i.e. <see cref="BenchmarkContainerType.Native"/> etc.</param> 243 /// <param name="args">Optional arguments that can be stored in a test implementation class.</param> 244 /// <remarks>This will run measurements with <see cref="IJob"/> or directly called on the main thread.</remarks> 245 public static unsafe void Run(int capacity, BenchmarkContainerType type, params int[] args) 246 { 247 var methods = new T(); 248 methods.SetParams(capacity, args); 249 250 switch (type) 251 { 252 case (BenchmarkContainerType)(BenchmarkContainerConfig.BCL): 253 object container = null; 254 BenchmarkMeasure.Measure(typeof(T), 255 BenchmarkContainerConfig.kCountWarmup, BenchmarkContainerConfig.kCountMeasure, 256 () => methods.MeasureBclContainer(container, 0), 257 () => container = methods.AllocBclContainer(capacity), () => container = methods.AllocBclContainer(-1)); 258 break; 259 case BenchmarkContainerType.Native: 260 BenchmarkMeasure.Measure(typeof(T), 261 BenchmarkContainerConfig.kCountWarmup, BenchmarkContainerConfig.kCountMeasure, 262 () => methods.MeasureNativeContainer(0, 0), 263 () => methods.AllocNativeContainer(capacity), () => methods.AllocNativeContainer(-1)); 264 break; 265 case BenchmarkContainerType.NativeBurstSafety: 266 BenchmarkMeasure.Measure(typeof(T), 267 BenchmarkContainerConfig.kCountWarmup, BenchmarkContainerConfig.kCountMeasure, 268 () => new NativeJobSafetyBurstST { methods = (T*)UnsafeUtility.AddressOf(ref methods) }.Run(), 269 () => methods.AllocNativeContainer(capacity), () => methods.AllocNativeContainer(-1)); 270 break; 271 case BenchmarkContainerType.NativeBurstNoSafety: 272 BenchmarkMeasure.Measure(typeof(T), 273 BenchmarkContainerConfig.kCountWarmup, BenchmarkContainerConfig.kCountMeasure, 274 () => new NativeJobBurstST { methods = (T*)UnsafeUtility.AddressOf(ref methods) }.Run(), 275 () => methods.AllocNativeContainer(capacity), () => methods.AllocNativeContainer(-1)); 276 break; 277 case BenchmarkContainerType.Unsafe: 278 BenchmarkMeasure.Measure(typeof(T), 279 BenchmarkContainerConfig.kCountWarmup, BenchmarkContainerConfig.kCountMeasure, 280 () => methods.MeasureUnsafeContainer(0, 0), 281 () => methods.AllocUnsafeContainer(capacity), () => methods.AllocUnsafeContainer(-1)); 282 break; 283 case BenchmarkContainerType.UnsafeBurstSafety: 284 BenchmarkMeasure.Measure(typeof(T), 285 BenchmarkContainerConfig.kCountWarmup, BenchmarkContainerConfig.kCountMeasure, 286 () => new UnsafeJobSafetyBurstST { methods = (T*)UnsafeUtility.AddressOf(ref methods) }.Run(), 287 () => methods.AllocUnsafeContainer(capacity), () => methods.AllocUnsafeContainer(-1)); 288 break; 289 case BenchmarkContainerType.UnsafeBurstNoSafety: 290 BenchmarkMeasure.Measure(typeof(T), 291 BenchmarkContainerConfig.kCountWarmup, BenchmarkContainerConfig.kCountMeasure, 292 () => new UnsafeJobBurstST { methods = (T*)UnsafeUtility.AddressOf(ref methods) }.Run(), 293 () => methods.AllocUnsafeContainer(capacity), () => methods.AllocUnsafeContainer(-1)); 294 break; 295 } 296 } 297 298 /// <summary> 299 /// Called from a typical performance test method to provide both Performance Framework measurements as well as 300 /// Benchmark Framework measurements. A typical usage is similar to: 301 /// <c>[Test, Performance]<br /> 302 /// [Category("Performance")]<br /> 303 /// public unsafe void ToNativeArray(<br /> 304 /// [Values(1, 2, 4, 8)] int workers,<br /> 305 /// [Values(100000, 1000000, 10000000)] int capacity,<br /> 306 /// [Values] BenchmarkContainerType type)<br /> 307 /// {<br /> 308 /// BenchmarkContainerRunner&lt;HashSetToNativeArray&gt;.Run(workers, capacity, type);<br /> 309 /// }</c> 310 /// </summary> 311 /// <param name="workers">The number of job workers to run performance tests on. These are duplicated across workers rather than split across workers.</param> 312 /// <param name="capacity">The capacity for the container(s) which will be passed to setup methods</param> 313 /// <param name="type">The benchmark or performance measurement type to run for containers i.e. <see cref="BenchmarkContainerType.Native"/> etc.</param> 314 /// <param name="args">Optional arguments that can be stored in a test implementation class.</param> 315 /// <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> 316 public static unsafe void Run(int workers, int capacity, BenchmarkContainerType type, params int[] args) 317 { 318 if (workers == 1) 319 Run(capacity, type, args); 320 else 321 RunMT(workers, capacity, type, args); 322 } 323 } 324}