A game about forced loneliness, made by TACStudios
1using Unity.PerformanceTesting; 2using Unity.PerformanceTesting.Benchmark; 3using Unity.Burst; 4using Unity.Collections.LowLevel.Unsafe; 5using Unity.Jobs; 6 7namespace Unity.Collections.PerformanceTests 8{ 9 /// <summary> 10 /// Specifies a class containing performance test methods which should be included in container benchmarking.<para /> 11 /// The values specified in this enum are unlikely to be needed in user code, but user code will specify the enum type 12 /// in a couple places:<para /> 13 /// <c>[Benchmark(typeof(BenchmarkContainerType))] // &lt;---- HERE<br /> 14 /// class FooContainerPerformanceTestMethods</c><para /> 15 /// and<para /> 16 /// <c>[Test, Performance]<br /> 17 /// public unsafe void ContainerPerfTestExample(<br /> 18 /// [Values(100000, 1000000, 10000000)] int capacity,<br /> 19 /// [Values] BenchmarkContainerType type) // &lt;---- HERE<br /> 20 /// {</c><para /> 21 /// Though values may be specified in the performance test method parameter, it is recommended to leave the argument implicitly 22 /// covering all enum values as seen in the example above. 23 /// </summary> 24 [BenchmarkComparison(BenchmarkContainerConfig.BCL, "{0} (BCL)")] 25 [BenchmarkComparisonDisplay(SampleUnit.Millisecond, 3, BenchmarkContainerConfig.kRankingMethod)] 26 public enum BenchmarkContainerType : int 27 { 28 /// <summary>Native container performance test will execute on a managed (not burst compiled) code path</summary> 29 [BenchmarkName("Native{0} (S)")] Native, 30 /// <summary>Native container performance test will execute on a burst compile code path, with safety checks enabled</summary> 31 [BenchmarkName("Native{0} (S+B)")] NativeBurstSafety, 32 /// <summary>Native container performance test will execute on a burst compile code path, with safety checks disabled</summary> 33 [BenchmarkName("Native{0} (B)")] NativeBurstNoSafety, 34 /// <summary>Unsafe container performance test will execute on a managed (not burst compiled) code path</summary> 35 [BenchmarkName("Unsafe{0} (S)")] Unsafe, 36 /// <summary>Unsafe container performance test will execute on a burst compile code path, with safety checks enabled</summary> 37 [BenchmarkName("Unsafe{0} (S+B)")] UnsafeBurstSafety, 38 /// <summary>Unsafe container performance test will execute on a burst compile code path, with safety checks disabled</summary> 39 [BenchmarkName("Unsafe{0} (B)")] UnsafeBurstNoSafety, 40 } 41 42 /// <summary> 43 /// Configuration settings for benchmarking containers. 44 /// </summary> 45 public static class BenchmarkContainerConfig 46 { 47 /// <summary> 48 /// An additional value to the enum values defined in <see cref="BenchmarkContainerType"/> which will not be included 49 /// in Performance Test Framework test generation but will be included in Benchmark Framework result generation. 50 /// </summary> 51 public const int BCL = -1; 52 53 internal const BenchmarkRankingStatistic kRankingMethod = BenchmarkRankingStatistic.Median; 54 internal const int kCountWarmup = 5; 55 internal const int kCountMeasure = 10; 56 57 /// <summary> 58 /// Prefix string for individual benchmark menu items 59 /// </summary> 60 public const string kMenuItemIndividual = "DOTS/Unity.Collections/Generate Individual Container Benchmark/"; 61 62#if UNITY_EDITOR 63 [UnityEditor.MenuItem("DOTS/Unity.Collections/Generate Container Benchmarks")] 64#endif 65 static void RunBenchmarks() => 66 BenchmarkGenerator.GenerateMarkdown( 67 "Containers", 68 typeof(BenchmarkContainerType), 69 "../../Packages/com.unity.collections/Documentation~/performance-comparison-containers.md", 70 $"The **{kRankingMethod} of {kCountMeasure} sample sets** is compared against the baseline on the far right side of the table." 71 + $"<br/>Multithreaded benchmarks divide the processing amongst the specified number of workers." 72 + $"<br/>{kCountWarmup} extra sample sets are run as warmup." 73 , 74 "Legend", 75 new string[] 76 { 77 "`(S)` = Safety Enabled", 78 "`(B)` = Burst Compiled *with Safety Disabled*", 79 "`(S+B)` = Burst Compiled *with Safety Enabled*", 80 "`(BCL)` = Base Class Library implementation (such as provided by Mono or .NET)", 81 "", 82 "*`italic`* results are for benchmarking comparison only; these are not included in standard Performance Framework tests", 83 }); 84 85#if UNITY_EDITOR 86 /// <summary> 87 /// Runs a benchmark for a particular class with Performance Test Framework/Benchmark methods. For calling 88 /// from custom menu items, for instance. 89 /// </summary> 90 /// <param name="testClassType"></param> 91 public static void RunBenchmark(System.Type testClassType) 92 { 93 string tempPath = System.IO.Path.Combine(System.IO.Path.GetDirectoryName( 94 UnityEditor.FileUtil.GetUniqueTempPathInProject()), $"Benchmark{testClassType.Name}.md"); 95 BenchmarkGenerator.GenerateMarkdown( 96 $"Individual -- {testClassType.Name}", 97 new System.Type[] { testClassType }, 98 tempPath, 99 $"The **{kRankingMethod} of {kCountMeasure} sample sets** is compared against the baseline on the far right side of the table." 100 + $"<br/>Multithreaded benchmarks divide the processing amongst the specified number of workers." 101 + $"<br/>{kCountWarmup} extra sample sets are run as warmup." 102 , 103 "Legend", 104 new string[] 105 { 106 "`(S)` = Safety Enabled", 107 "`(B)` = Burst Compiled *with Safety Disabled*", 108 "`(S+B)` = Burst Compiled *with Safety Enabled*", 109 "`(BCL)` = Base Class Library implementation (such as provided by Mono or .NET)", 110 "", 111 "*`italic`* results are for benchmarking comparison only; these are not included in standard Performance Framework tests", 112 }); 113 UnityEditor.EditorUtility.RevealInFinder(tempPath); 114 } 115#endif 116 } 117 118 /// <summary> 119 /// Interface to implement container performance tests which will run using <see cref="BenchmarkContainerRunner{T}.Run(int, BenchmarkContainerType, int[])"/>. 120 /// Deriving tests from this interface enables both Performance Test Framework and Benchmark Framework to generate and run 121 /// tests for the contexts described by <see cref="BenchmarkContainerType"/>. 122 /// </summary> 123 public interface IBenchmarkContainer 124 { 125 /// <summary> 126 /// Override this to add extra int arguments to a performance test implementation as fields in the implementing type. These arguments 127 /// are optionally passed in through <see cref="BenchmarkContainerRunner{T}.Run(int, BenchmarkContainerType, int[])"/>. 128 /// </summary> 129 /// <param name="capacity">The initial capacity to requested for the container.</param> 130 /// <param name="args">A variable number of extra arguments to passed through to the test implementation</param> 131 public void SetParams(int capacity, params int[] args) { } 132 133 /// <summary> 134 /// Called during setup for each measurement in a sample set with the capacity to allocate to the native container 135 /// when the benchmark type is <see cref="BenchmarkContainerType.Native"/>, <see cref="BenchmarkContainerType.NativeBurstNoSafety"/>, 136 /// or <see cref="BenchmarkContainerType.NativeBurstSafety"/>.<para /> 137 /// This is also called during teardown for each measurement in a sample set with '-1' to indicate freeing the container. 138 /// </summary> 139 /// <param name="capacity">The capacity to allocate for the managed container. Capacity of 0 will still create a container, 140 /// but it will be empty. A capacity of -1 will dispose the container and free associated allocation(s).</param> 141 public void AllocNativeContainer(int capacity); 142 143 /// <summary> 144 /// Called during setup for each measurement in a sample set with the capacity to allocate to the unsafe container 145 /// when the benchmark type is <see cref="BenchmarkContainerType.Unsafe"/>, <see cref="BenchmarkContainerType.UnsafeBurstNoSafety"/>, 146 /// or <see cref="BenchmarkContainerType.UnsafeBurstSafety"/>.<para /> 147 /// This is also called during teardown for each measurement in a sample set with '-1' to indicate freeing the container. 148 /// </summary> 149 /// <param name="capacity">The capacity to allocate for the managed container. Capacity of 0 will still create a container, 150 /// but it will be empty. A capacity of -1 will dispose the container and free associated allocation(s).</param> 151 public void AllocUnsafeContainer(int capacity); // capacity 0 frees 152 153 /// <summary> 154 /// Called during setup for each measurement in a sample set with the capacity to allocate to the managed container 155 /// when the benchmark type is <see cref="BenchmarkContainerConfig.BCL"/>.<para /> 156 /// This is also called during teardown for each measurement in a sample set with '-1' to indicate freeing the container. 157 /// </summary> 158 /// <param name="capacity">The capacity to allocate for the managed container. Capacity of 0 will still create a container, 159 /// but it will be empty. A capacity of -1 will dispose the container and free associated allocation(s).</param> 160 /// <returns>A reference to the allocated container when capacity &gt;= 0, and `null` when capacity &lt; 0.</returns> 161 public object AllocBclContainer(int capacity); 162 163 /// <summary> 164 /// The code which will be executed during performance measurement. This should usually be general enough to 165 /// work with any native container. 166 /// </summary> 167 public void MeasureNativeContainer(); 168 169 /// <summary> 170 /// The code which will be executed during performance measurement. This should usually be general enough to 171 /// work with any unsafe container. 172 /// </summary> 173 public void MeasureUnsafeContainer(); 174 175 /// <summary> 176 /// The code which will be executed during performance measurement. This should usually be general enough to 177 /// work with any managed container provided by the Base Class Library (BCL). 178 /// </summary> 179 /// <param name="container">A reference to the managed container allocated in <see cref="AllocBclContainer(int)"/></param> 180 public void MeasureBclContainer(object container); 181 } 182 183 /// <summary> 184 /// Provides the API for running container based Performance Framework tests and Benchmark Framework measurements. 185 /// This will typically be the sole call from a performance test. See <see cref="Run(int, BenchmarkContainerType, int[])"/> 186 /// for more information. 187 /// </summary> 188 /// <typeparam name="T">An implementation conforming to the <see cref="IBenchmarkContainer"/> interface for running container performance tests and benchmarks.</typeparam> 189 [BurstCompile(CompileSynchronously = true)] 190 public static class BenchmarkContainerRunner<T> where T : unmanaged, IBenchmarkContainer 191 { 192 [BurstCompile(CompileSynchronously = true, DisableSafetyChecks = true)] 193 unsafe struct NativeJobBurstST : IJob 194 { 195 [NativeDisableUnsafePtrRestriction] public T* methods; 196 public void Execute() => methods->MeasureNativeContainer(); 197 } 198 [BurstCompile(CompileSynchronously = true, DisableSafetyChecks = false)] 199 unsafe struct NativeJobSafetyBurstST : IJob 200 { 201 [NativeDisableUnsafePtrRestriction] public T* methods; 202 public void Execute() => methods->MeasureNativeContainer(); 203 } 204 205 [BurstCompile(CompileSynchronously = true, DisableSafetyChecks = true)] 206 unsafe struct UnsafeJobBurstST : IJob 207 { 208 [NativeDisableUnsafePtrRestriction] public T* methods; 209 public void Execute() => methods->MeasureUnsafeContainer(); 210 } 211 [BurstCompile(CompileSynchronously = true, DisableSafetyChecks = false)] 212 unsafe struct UnsafeJobSafetyBurstST : IJob 213 { 214 [NativeDisableUnsafePtrRestriction] public T* methods; 215 public void Execute() => methods->MeasureUnsafeContainer(); 216 } 217 218 /// <summary> 219 /// Called from a typical performance test method to provide both Performance Framework measurements as well as 220 /// Benchmark Framework measurements. A typical usage is similar to: 221 /// <c>[Test, Performance]<br /> 222 /// [Category("Performance")]<br /> 223 /// public unsafe void ToNativeArray(<br /> 224 /// [Values(100000, 1000000, 10000000)] int capacity,<br /> 225 /// [Values] BenchmarkContainerType type)<br /> 226 /// {<br /> 227 /// BenchmarkContainerRunner&lt;HashSetToNativeArray&gt;.RunST(capacity, type);<br /> 228 /// }</c> 229 /// </summary> 230 /// <param name="capacity">The capacity for the container(s) which will be passed to setup methods</param> 231 /// <param name="type">The benchmark or performance measurement type to run for containers i.e. <see cref="BenchmarkContainerType.Native"/> etc.</param> 232 /// <param name="args">Optional arguments that can be stored in a test implementation class.</param> 233 /// <remarks>This will run measurements with <see cref="IJob"/> or directly called on the main thread.</remarks> 234 public static unsafe void Run(int capacity, BenchmarkContainerType type, params int[] args) 235 { 236 var methods = new T(); 237 methods.SetParams(capacity, args); 238 239 switch (type) 240 { 241 case (BenchmarkContainerType)(BenchmarkContainerConfig.BCL): 242 object container = null; 243 BenchmarkMeasure.Measure(typeof(T), 244 BenchmarkContainerConfig.kCountWarmup, BenchmarkContainerConfig.kCountMeasure, 245 () => methods.MeasureBclContainer(container), 246 () => container = methods.AllocBclContainer(capacity), () => container = methods.AllocBclContainer(-1)); 247 break; 248 case BenchmarkContainerType.Native: 249 BenchmarkMeasure.Measure(typeof(T), 250 BenchmarkContainerConfig.kCountWarmup, BenchmarkContainerConfig.kCountMeasure, 251 () => methods.MeasureNativeContainer(), 252 () => methods.AllocNativeContainer(capacity), () => methods.AllocNativeContainer(-1)); 253 break; 254 case BenchmarkContainerType.NativeBurstSafety: 255 BenchmarkMeasure.Measure(typeof(T), 256 BenchmarkContainerConfig.kCountWarmup, BenchmarkContainerConfig.kCountMeasure, 257 () => new NativeJobSafetyBurstST { methods = (T*)UnsafeUtility.AddressOf(ref methods) }.Run(), 258 () => methods.AllocNativeContainer(capacity), () => methods.AllocNativeContainer(-1)); 259 break; 260 case BenchmarkContainerType.NativeBurstNoSafety: 261 BenchmarkMeasure.Measure(typeof(T), 262 BenchmarkContainerConfig.kCountWarmup, BenchmarkContainerConfig.kCountMeasure, 263 () => new NativeJobBurstST { methods = (T*)UnsafeUtility.AddressOf(ref methods) }.Run(), 264 () => methods.AllocNativeContainer(capacity), () => methods.AllocNativeContainer(-1)); 265 break; 266 case BenchmarkContainerType.Unsafe: 267 BenchmarkMeasure.Measure(typeof(T), 268 BenchmarkContainerConfig.kCountWarmup, BenchmarkContainerConfig.kCountMeasure, 269 () => methods.MeasureUnsafeContainer(), 270 () => methods.AllocUnsafeContainer(capacity), () => methods.AllocUnsafeContainer(-1)); 271 break; 272 case BenchmarkContainerType.UnsafeBurstSafety: 273 BenchmarkMeasure.Measure(typeof(T), 274 BenchmarkContainerConfig.kCountWarmup, BenchmarkContainerConfig.kCountMeasure, 275 () => new UnsafeJobSafetyBurstST { methods = (T*)UnsafeUtility.AddressOf(ref methods) }.Run(), 276 () => methods.AllocUnsafeContainer(capacity), () => methods.AllocUnsafeContainer(-1)); 277 break; 278 case BenchmarkContainerType.UnsafeBurstNoSafety: 279 BenchmarkMeasure.Measure(typeof(T), 280 BenchmarkContainerConfig.kCountWarmup, BenchmarkContainerConfig.kCountMeasure, 281 () => new UnsafeJobBurstST { methods = (T*)UnsafeUtility.AddressOf(ref methods) }.Run(), 282 () => methods.AllocUnsafeContainer(capacity), () => methods.AllocUnsafeContainer(-1)); 283 break; 284 } 285 } 286 } 287}