Nenhuma descrição
Você não pode selecionar mais de 25 tópicos Os tópicos devem começar com uma letra ou um número, podem incluir traços ('-') e podem ter até 35 caracteres.

MethodMeasurement.cs 16KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456
  1. using System;
  2. using System.Collections.Generic;
  3. using Unity.PerformanceTesting.Data;
  4. using Unity.PerformanceTesting.Runtime;
  5. using Unity.PerformanceTesting.Exceptions;
  6. using Unity.PerformanceTesting.Meters;
  7. using Unity.PerformanceTesting.Statistics;
  8. using UnityEngine;
  9. using UnityEngine.Profiling;
  10. namespace Unity.PerformanceTesting.Measurements
  11. {
  12. /// <summary>
  13. /// Used as a helper class to sample execution time of methods. Uses fluent pattern to build and needs to be executed with Run method.
  14. /// </summary>
  15. public class MethodMeasurement
  16. {
  17. internal const int k_MeasurementCount = 9;
  18. private const int k_MinMeasurementTimeMs = 100;
  19. private const int k_MinWarmupTimeMs = 100;
  20. private const int k_ProbingMultiplier = 4;
  21. private const int k_MaxIterations = 10000;
  22. internal const int k_MaxDynamicMeasurements = 1000;
  23. private const double k_DefaultMaxRelativeError = 0.02;
  24. private const ConfidenceLevel k_DefaultConfidenceLevel = ConfidenceLevel.L99;
  25. private const OutlierMode k_DefaultOutlierMode = OutlierMode.Remove;
  26. private readonly Action m_Action;
  27. private readonly List<SampleGroup> m_SampleGroups = new List<SampleGroup>();
  28. private readonly Recorder m_GCRecorder;
  29. private Action m_Setup;
  30. private Action m_Cleanup;
  31. private SampleGroup m_SampleGroup = new SampleGroup("Time", SampleUnit.Millisecond, false);
  32. private SampleGroup m_SampleGroupGC = new SampleGroup("Time.GC()", SampleUnit.Undefined, false);
  33. private int m_WarmupCount;
  34. private int m_MeasurementCount;
  35. internal bool m_DynamicMeasurementCount;
  36. private double m_MaxRelativeError = k_DefaultMaxRelativeError;
  37. private ConfidenceLevel m_ConfidenceLevel = k_DefaultConfidenceLevel;
  38. private OutlierMode m_OutlierMode = k_DefaultOutlierMode;
  39. private int m_IterationCount = 1;
  40. private bool m_GC;
  41. private IStopWatch m_Watch;
  42. /// <summary>
  43. /// Initializes a method measurement.
  44. /// </summary>
  45. /// <param name="action">Method to be measured.</param>
  46. public MethodMeasurement(Action action)
  47. {
  48. m_Action = action;
  49. m_GCRecorder = Recorder.Get("GC.Alloc");
  50. m_GCRecorder.enabled = false;
  51. if (m_Watch == null) m_Watch = new StopWatch();
  52. }
  53. internal MethodMeasurement StopWatch(IStopWatch watch)
  54. {
  55. m_Watch = watch;
  56. return this;
  57. }
  58. /// <summary>
  59. /// Will record provided profiler markers once per frame.
  60. /// </summary>
  61. /// <param name="profilerMarkerNames">Profiler marker names as in profiler window.</param>
  62. /// <returns></returns>
  63. public MethodMeasurement ProfilerMarkers(params string[] profilerMarkerNames)
  64. {
  65. if (profilerMarkerNames == null) return this;
  66. foreach (var marker in profilerMarkerNames)
  67. {
  68. var sampleGroup = new SampleGroup(marker, SampleUnit.Nanosecond, false);
  69. sampleGroup.GetRecorder();
  70. sampleGroup.Recorder.enabled = false;
  71. m_SampleGroups.Add(sampleGroup);
  72. }
  73. return this;
  74. }
  75. /// <summary>
  76. /// Will record provided profiler markers once per frame with additional control over the SampleUnit.
  77. /// </summary>
  78. /// <param name="sampleGroups">List of SampleGroups where a name matches the profiler marker and desired SampleUnit.</param>
  79. /// <returns></returns>
  80. public MethodMeasurement ProfilerMarkers(params SampleGroup[] sampleGroups)
  81. {
  82. if (sampleGroups == null){ return this;}
  83. foreach (var sampleGroup in sampleGroups)
  84. {
  85. sampleGroup.GetRecorder();
  86. sampleGroup.Recorder.enabled = false;
  87. m_SampleGroups.Add(sampleGroup);
  88. }
  89. return this;
  90. }
  91. /// <summary>
  92. /// Overrides the default SampleGroup of "Time".
  93. /// </summary>
  94. /// <param name="name">Desired name for measurement SampleGroup.</param>
  95. /// <returns></returns>
  96. public MethodMeasurement SampleGroup(string name)
  97. {
  98. m_SampleGroup = new SampleGroup(name, SampleUnit.Millisecond, false);
  99. m_SampleGroupGC = new SampleGroup(name + ".GC()", SampleUnit.Undefined, false);
  100. return this;
  101. }
  102. /// <summary>
  103. /// Overrides the default SampleGroup.
  104. /// </summary>
  105. /// <param name="sampleGroup">SampleGroup with your desired name and unit.</param>
  106. /// <returns></returns>
  107. public MethodMeasurement SampleGroup(SampleGroup sampleGroup)
  108. {
  109. m_SampleGroup = sampleGroup;
  110. m_SampleGroupGC = new SampleGroup(sampleGroup.Name + ".GC()", SampleUnit.Undefined, false);
  111. return this;
  112. }
  113. /// <summary>
  114. /// Count of times to execute before measurements are collected. If unspecified, a default warmup will be assigned.
  115. /// </summary>
  116. /// <param name="count">Count of warmup iterations to execute.</param>
  117. /// <returns></returns>
  118. public MethodMeasurement WarmupCount(int count)
  119. {
  120. m_WarmupCount = count;
  121. return this;
  122. }
  123. /// <summary>
  124. /// Specifies the amount of method executions for a single measurement.
  125. /// </summary>
  126. /// <param name="count">Count of method executions.</param>
  127. /// <returns></returns>
  128. public MethodMeasurement IterationsPerMeasurement(int count)
  129. {
  130. m_IterationCount = count;
  131. return this;
  132. }
  133. /// <summary>
  134. /// Specifies the number of measurements to take.
  135. /// </summary>
  136. /// <param name="count">Count of measurements to take.</param>
  137. /// <returns></returns>
  138. public MethodMeasurement MeasurementCount(int count)
  139. {
  140. m_MeasurementCount = count;
  141. return this;
  142. }
  143. /// <summary>
  144. /// Dynamically find a suitable measurement count based on the margin of error of the samples.
  145. /// The measurements will stop once a certain amount of samples (specified by a confidence interval)
  146. /// falls within an acceptable error range from the result (defined by a relative error of the mean).
  147. /// A default margin of error range of 2% and a default confidence interval of 99% will be used.
  148. /// </summary>
  149. /// <param name="outlierMode">Outlier mode allows to include or exclude outliers when evaluating the stop criterion.</param>
  150. /// <returns></returns>
  151. public MethodMeasurement DynamicMeasurementCount(OutlierMode outlierMode = k_DefaultOutlierMode)
  152. {
  153. m_DynamicMeasurementCount = true;
  154. m_OutlierMode = outlierMode;
  155. return this;
  156. }
  157. /// <summary>
  158. /// Dynamically find a suitable measurement count based on the margin of error of the samples.
  159. /// The measurements will stop once a certain amount of samples (specified by a confidence interval)
  160. /// falls within an acceptable error range from the result (defined by a relative error of the mean).
  161. /// </summary>
  162. /// <param name="maxRelativeError">The maximum relative error of the mean that the margin of error must fall into.</param>
  163. /// <param name="confidenceLevel">The confidence interval which will be used to calculate the margin of error.</param>
  164. /// <param name="outlierMode">Outlier mode allows to include or exclude outliers when evaluating the stop criterion.</param>
  165. /// <returns></returns>
  166. public MethodMeasurement DynamicMeasurementCount(double maxRelativeError, ConfidenceLevel confidenceLevel = k_DefaultConfidenceLevel,
  167. OutlierMode outlierMode = k_DefaultOutlierMode)
  168. {
  169. m_MaxRelativeError = maxRelativeError;
  170. m_ConfidenceLevel = confidenceLevel;
  171. m_DynamicMeasurementCount = true;
  172. m_OutlierMode = outlierMode;
  173. return this;
  174. }
  175. /// <summary>
  176. /// Used to provide a cleanup method which will not be measured.
  177. /// </summary>
  178. /// <param name="action">Cleanup method to execute.</param>
  179. /// <returns></returns>
  180. public MethodMeasurement CleanUp(Action action)
  181. {
  182. m_Cleanup = action;
  183. return this;
  184. }
  185. /// <summary>
  186. /// Used to provide a setup method which will run before the measurement.
  187. /// </summary>
  188. /// <param name="action">Setup method to execute.</param>
  189. /// <returns></returns>
  190. public MethodMeasurement SetUp(Action action)
  191. {
  192. m_Setup = action;
  193. return this;
  194. }
  195. /// <summary>
  196. /// Enables recording of garbage collector calls.
  197. /// </summary>
  198. /// <returns></returns>
  199. public MethodMeasurement GC()
  200. {
  201. m_GC = true;
  202. return this;
  203. }
  204. /// <summary>
  205. /// Executes the measurement with given parameters. When MeasurementCount is not provided, a probing method will run to determine desired measurement counts.
  206. /// </summary>
  207. public void Run()
  208. {
  209. ValidateCorrectDynamicMeasurementCountUsage();
  210. SettingsOverride();
  211. var settingsCount = RunSettings.Instance.MeasurementCount;
  212. if (m_MeasurementCount > 0 || settingsCount > -1)
  213. {
  214. Warmup(m_WarmupCount);
  215. RunForIterations(m_IterationCount, m_MeasurementCount, useAverage: false);
  216. return;
  217. }
  218. if (m_DynamicMeasurementCount)
  219. {
  220. Warmup(m_WarmupCount);
  221. RunForIterations(m_IterationCount);
  222. return;
  223. }
  224. var iterations = Probing();
  225. RunForIterations(iterations, k_MeasurementCount, useAverage: true);
  226. }
  227. private void ValidateCorrectDynamicMeasurementCountUsage()
  228. {
  229. if (!m_DynamicMeasurementCount)
  230. return;
  231. if (m_MeasurementCount > 0)
  232. {
  233. m_DynamicMeasurementCount = false;
  234. Debug.LogWarning("DynamicMeasurementCount will be ignored because MeasurementCount was specified.");
  235. }
  236. }
  237. /// <summary>
  238. /// Overrides measurement count based on performance run settings
  239. /// </summary>
  240. private void SettingsOverride()
  241. {
  242. var count = RunSettings.Instance.MeasurementCount;
  243. if (count < 0) { return; }
  244. m_MeasurementCount = count;
  245. m_WarmupCount = m_WarmupCount > 0 ? count : 0;
  246. m_DynamicMeasurementCount = false;
  247. }
  248. private void RunForIterations(int iterations, int measurements, bool useAverage)
  249. {
  250. EnableMarkers();
  251. for (var j = 0; j < measurements; j++)
  252. {
  253. var executionTime = iterations == 1 ? ExecuteSingleIteration() : ExecuteForIterations(iterations);
  254. if (useAverage) executionTime /= iterations;
  255. var delta = Utils.ConvertSample(SampleUnit.Millisecond, m_SampleGroup.Unit, executionTime);
  256. Measure.Custom(m_SampleGroup, delta);
  257. }
  258. DisableAndMeasureMarkers();
  259. }
  260. private void RunForIterations(int iterations)
  261. {
  262. EnableMarkers();
  263. while(true)
  264. {
  265. var executionTime = iterations == 1 ? ExecuteSingleIteration() : ExecuteForIterations(iterations);
  266. var delta = Utils.ConvertSample(SampleUnit.Millisecond, m_SampleGroup.Unit, executionTime);
  267. Measure.Custom(m_SampleGroup, delta);
  268. if (SampleCountFulfillsRequirements())
  269. break;
  270. }
  271. DisableAndMeasureMarkers();
  272. }
  273. private void EnableMarkers()
  274. {
  275. foreach (var sampleGroup in m_SampleGroups)
  276. {
  277. sampleGroup.Recorder.enabled = true;
  278. }
  279. }
  280. private void DisableAndMeasureMarkers()
  281. {
  282. foreach (var sampleGroup in m_SampleGroups)
  283. {
  284. sampleGroup.Recorder.enabled = false;
  285. var sample = sampleGroup.Recorder.elapsedNanoseconds;
  286. var blockCount = sampleGroup.Recorder.sampleBlockCount;
  287. if(blockCount == 0) continue;
  288. var delta = Utils.ConvertSample(SampleUnit.Nanosecond, sampleGroup.Unit, sample);
  289. Measure.Custom(sampleGroup, delta / blockCount);
  290. }
  291. }
  292. private bool SampleCountFulfillsRequirements()
  293. {
  294. var samples = m_SampleGroup.Samples;
  295. var sampleCount = samples.Count;
  296. var statistics = MeasurementsStatistics.Calculate(samples, m_OutlierMode, m_ConfidenceLevel);
  297. var actualError = statistics.MarginOfError;
  298. var maxError = m_MaxRelativeError * statistics.Mean;
  299. if (sampleCount >= k_MeasurementCount && actualError < maxError)
  300. return true;
  301. if (sampleCount >= k_MaxDynamicMeasurements)
  302. return true;
  303. return false;
  304. }
  305. private int Probing()
  306. {
  307. var executionTime = 0.0D;
  308. var iterations = 1;
  309. if (m_WarmupCount > 0)
  310. throw new PerformanceTestException(
  311. "Please provide MeasurementCount or remove WarmupCount in your usage of Measure.Method");
  312. while (executionTime < k_MinWarmupTimeMs)
  313. {
  314. executionTime = m_Watch.Split();
  315. Warmup(iterations);
  316. executionTime = m_Watch.Split() - executionTime;
  317. if (executionTime < k_MinWarmupTimeMs)
  318. {
  319. iterations *= k_ProbingMultiplier;
  320. }
  321. }
  322. if (iterations == 1)
  323. {
  324. ExecuteActionWithCleanupSetup();
  325. ExecuteActionWithCleanupSetup();
  326. return 1;
  327. }
  328. var deisredIterationsCount =
  329. Mathf.Clamp((int) (k_MinMeasurementTimeMs * iterations / executionTime), 1, k_MaxIterations);
  330. return deisredIterationsCount;
  331. }
  332. private void Warmup(int iterations)
  333. {
  334. for (var i = 0; i < iterations; i++)
  335. {
  336. ExecuteForIterations(m_IterationCount);
  337. }
  338. }
  339. private double ExecuteActionWithCleanupSetup()
  340. {
  341. m_Setup?.Invoke();
  342. var executionTime = m_Watch.Split();
  343. m_Action.Invoke();
  344. executionTime = m_Watch.Split() - executionTime;
  345. m_Cleanup?.Invoke();
  346. return executionTime;
  347. }
  348. private double ExecuteSingleIteration()
  349. {
  350. if (m_GC) StartGCRecorder();
  351. m_Setup?.Invoke();
  352. var executionTime = m_Watch.Split();
  353. m_Action.Invoke();
  354. executionTime = m_Watch.Split() - executionTime;
  355. m_Cleanup?.Invoke();
  356. if (m_GC) EndGCRecorderAndMeasure(1);
  357. return executionTime;
  358. }
  359. private double ExecuteForIterations(int iterations)
  360. {
  361. if (m_GC) StartGCRecorder();
  362. var executionTime = 0.0D;
  363. if (m_Cleanup != null || m_Setup != null)
  364. {
  365. for (var i = 0; i < iterations; i++)
  366. {
  367. executionTime += ExecuteActionWithCleanupSetup();
  368. }
  369. }
  370. else
  371. {
  372. executionTime = m_Watch.Split();
  373. for (var i = 0; i < iterations; i++)
  374. {
  375. m_Action.Invoke();
  376. }
  377. executionTime = m_Watch.Split() - executionTime;
  378. }
  379. if (m_GC) EndGCRecorderAndMeasure(iterations);
  380. return executionTime;
  381. }
  382. private void StartGCRecorder()
  383. {
  384. System.GC.Collect();
  385. m_GCRecorder.enabled = false;
  386. m_GCRecorder.enabled = true;
  387. }
  388. private void EndGCRecorderAndMeasure(int iterations)
  389. {
  390. m_GCRecorder.enabled = false;
  391. Measure.Custom(m_SampleGroupGC, (double) m_GCRecorder.sampleBlockCount / iterations);
  392. }
  393. }
  394. }