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))] // <---- 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) // <---- 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 >= 0, and `null` when capacity < 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<HashSetToNativeArray>.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}