Ingen beskrivning
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

BenchmarkRunner.cs 30KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Reflection;
  4. using Unity.Collections;
  5. using Unity.Collections.LowLevel.Unsafe;
  6. namespace Unity.PerformanceTesting.Benchmark
  7. {
  8. internal static class BenchmarkRunner
  9. {
  10. static string progressTitle;
  11. static void StartProgress(string title, int typeIndex, int typeCount, string typeName) =>
  12. progressTitle = $"Benchmarking {title} {typeIndex + 1}/{typeCount} - {typeName}";
  13. static void EndProgress()
  14. {
  15. #if UNITY_EDITOR
  16. UnityEditor.EditorUtility.ClearProgressBar();
  17. #endif
  18. }
  19. static void SetProgressText(string text, float unitProgress)
  20. {
  21. #if UNITY_EDITOR
  22. if (UnityEditor.EditorUtility.DisplayCancelableProgressBar(progressTitle, text, unitProgress))
  23. throw new Exception("User cancelled benchmark operation");
  24. #endif
  25. }
  26. /// <summary>
  27. /// Contains a combination of a BenchmarkComparison attributed enum and the Type with perf. measurements
  28. /// to determine names for the benchmark.
  29. ///
  30. /// Also contains reflected info on the enum defined and external benchmark values used to organize
  31. /// benchmark tests and results, though this will not vary between different Types with perf. measurments.
  32. /// These constant values are also associated with a classification of enum-defined vs external, and
  33. /// baseline vs not.
  34. ///
  35. /// There may only be one baseline per benchmark comparison type.
  36. /// </summary>
  37. class BenchmarkComparisonTypeData
  38. {
  39. public string defaultName;
  40. public Type enumType;
  41. public string[] names;
  42. public int[] values;
  43. public BenchmarkResultType[] resultTypes;
  44. public SampleUnit resultUnit;
  45. public int resultDecimalPlaces;
  46. public BenchmarkRankingStatistic resultStatistic;
  47. public BenchmarkComparisonTypeData(int variants)
  48. {
  49. names = new string[variants];
  50. values = new int[variants];
  51. resultTypes = new BenchmarkResultType[variants];
  52. enumType = null;
  53. defaultName = null;
  54. resultUnit = SampleUnit.Millisecond;
  55. resultDecimalPlaces = 3;
  56. resultStatistic = BenchmarkRankingStatistic.Median;
  57. }
  58. }
  59. /// <summary>
  60. /// Given a System.Type that contains performance test methods, reflect the setup to a benchmark comparison.
  61. /// Throws on any errors with the setup.
  62. /// </summary>
  63. unsafe static BenchmarkComparisonTypeData GatherComparisonStructure(Type t)
  64. {
  65. //--------
  66. // Determine and validate the benchmark comparison this type is intended for
  67. //--------
  68. Type benchmarkEnumType = null;
  69. foreach(var attributeData in t.GetCustomAttributesData())
  70. {
  71. if (attributeData.AttributeType == typeof(BenchmarkAttribute))
  72. {
  73. benchmarkEnumType = (Type)attributeData.ConstructorArguments[0].Value;
  74. break;
  75. }
  76. }
  77. if (benchmarkEnumType == null)
  78. throw new ArgumentException($"Exactly one [{nameof(BenchmarkAttribute)}] must exist on the type {t.Name} to generate benchmark data");
  79. // Find the baseline and the formatting for its title name (could be external to the enum or included)
  80. CustomAttributeData attrBenchmarkComparison = null;
  81. List<CustomAttributeData> attrBenchmarkComparisonExternal = new List<CustomAttributeData>();
  82. CustomAttributeData attrBenchmarkFormat = null;
  83. foreach (var attributeData in benchmarkEnumType.GetCustomAttributesData())
  84. {
  85. if (attributeData.AttributeType == typeof(BenchmarkComparisonAttribute))
  86. {
  87. attrBenchmarkComparison = attributeData;
  88. }
  89. // Find any other external comparisons
  90. else if (attributeData.AttributeType == typeof(BenchmarkComparisonExternalAttribute))
  91. {
  92. attrBenchmarkComparisonExternal.Add(attributeData);
  93. }
  94. // Find optional formatting of table results
  95. else if (attributeData.AttributeType == typeof(BenchmarkComparisonDisplayAttribute))
  96. {
  97. attrBenchmarkFormat = attributeData;
  98. }
  99. }
  100. if (attrBenchmarkComparison == null)
  101. throw new ArgumentException($"Exactly one [{nameof(BenchmarkComparisonAttribute)}] must exist on the enum {benchmarkEnumType.Name} to generate benchmark data and define the baseline");
  102. //--------
  103. // Collect values and name formatting for enum and external
  104. //--------
  105. // Enum field values
  106. var enumFields = benchmarkEnumType.GetFields(BindingFlags.Static | BindingFlags.Public);
  107. var enumCount = enumFields.Length;
  108. var enumValues = stackalloc int[enumCount];
  109. var enumValuesSet = new HashSet<int>(enumCount);
  110. for (int i = 0; i < enumCount; i++)
  111. {
  112. int value = (int)enumFields[i].GetRawConstantValue();
  113. enumValues[i] = value;
  114. enumValuesSet.Add(value);
  115. }
  116. var enumFormats = new List<string>(enumCount);
  117. foreach(var x in enumFields)
  118. {
  119. int oldCount = enumFormats.Count;
  120. foreach (var attributeData in x.GetCustomAttributesData())
  121. {
  122. if (attributeData.AttributeType == typeof(BenchmarkNameAttribute))
  123. {
  124. enumFormats.Add((string)attributeData.ConstructorArguments[0].Value);
  125. break;
  126. }
  127. }
  128. if (oldCount == enumFormats.Count)
  129. throw new ArgumentException($"{x.Name} as well as all other enum values in {benchmarkEnumType.Name} must have a single [{nameof(BenchmarkNameAttribute)}] defined");
  130. }
  131. // External values
  132. var externalValues = new List<int>(attrBenchmarkComparisonExternal.Count);
  133. foreach(var x in attrBenchmarkComparisonExternal)
  134. {
  135. var externalValue = (int)x.ConstructorArguments[0].Value;
  136. if (enumValuesSet.Contains(externalValue))
  137. throw new ArgumentException($"Externally-defined benchmark values for {benchmarkEnumType.Name} must not be a duplicate of another enum-defined or externally-defined benchmark value for {benchmarkEnumType.Name}");
  138. }
  139. var externalFormats = new List<string>(attrBenchmarkComparisonExternal.Count);
  140. foreach(var x in attrBenchmarkComparisonExternal)
  141. {
  142. externalFormats.Add((string)x.ConstructorArguments[1].Value);
  143. }
  144. var externalCount = externalValues.Count;
  145. // Baseline value
  146. int baselineValue = (int)attrBenchmarkComparison.ConstructorArguments[0].Value;
  147. string externalBaselineFormat = null;
  148. if (attrBenchmarkComparison.ConstructorArguments.Count == 1)
  149. {
  150. if (!enumValuesSet.Contains(baselineValue))
  151. throw new ArgumentException($"{baselineValue} not found in enum {benchmarkEnumType.Name}. Either specify an existing value as the baseline, or add a formatting string for the externally defined baseline value.");
  152. }
  153. else
  154. {
  155. if (enumValuesSet.Contains(baselineValue))
  156. throw new ArgumentException($"To specify an enum-defined benchmark baseline in {benchmarkEnumType.Name}, pass only the argument {baselineValue} without a name, as the name requires definition in the enum");
  157. if (externalValues.Contains(baselineValue))
  158. throw new ArgumentException($"To specify an external-defined benchmark baseline in {benchmarkEnumType.Name}, define only in [{nameof(BenchmarkComparisonAttribute)}] and omit also defining with [{nameof(BenchmarkComparisonExternalAttribute)}]");
  159. externalBaselineFormat = (string)attrBenchmarkComparison.ConstructorArguments[1].Value;
  160. }
  161. // Total
  162. int variantCount = enumCount + externalCount + (externalBaselineFormat == null ? 0 : 1);
  163. //--------
  164. // Collect name overrides on the specific type with benchmarking methods
  165. //--------
  166. string defaultNameOverride = null;
  167. var nameOverride = new Dictionary<int, string>();
  168. foreach (var attr in t.CustomAttributes)
  169. {
  170. if (attr.AttributeType == typeof(BenchmarkNameOverrideAttribute))
  171. {
  172. if (attr.ConstructorArguments.Count == 1)
  173. {
  174. if (defaultNameOverride != null)
  175. throw new ArgumentException($"No more than one default name override is allowed for {t.Name} using [{nameof(BenchmarkNameOverrideAttribute)}]");
  176. defaultNameOverride = (string)attr.ConstructorArguments[0].Value;
  177. }
  178. else
  179. {
  180. int valueOverride = (int)attr.ConstructorArguments[0].Value;
  181. if (nameOverride.ContainsKey(valueOverride))
  182. throw new ArgumentException($"No more than one name override is allowed for benchmark comparison value {valueOverride} using [{nameof(BenchmarkNameOverrideAttribute)}]");
  183. nameOverride[valueOverride] = (string)attr.ConstructorArguments[1].Value;
  184. }
  185. }
  186. }
  187. //--------
  188. // Record all the information
  189. //--------
  190. var ret = new BenchmarkComparisonTypeData(variantCount);
  191. ret.defaultName = defaultNameOverride ?? t.Name;
  192. ret.enumType = benchmarkEnumType;
  193. // Result optional custom formatting
  194. if (attrBenchmarkFormat != null)
  195. {
  196. ret.resultUnit = (SampleUnit)attrBenchmarkFormat.ConstructorArguments[0].Value;
  197. ret.resultDecimalPlaces = (int)attrBenchmarkFormat.ConstructorArguments[1].Value;
  198. ret.resultStatistic = (BenchmarkRankingStatistic)attrBenchmarkFormat.ConstructorArguments[2].Value;
  199. }
  200. // Enum field values
  201. for (int i = 0; i < enumCount; i++)
  202. {
  203. ret.names[i] = enumFormats[i];
  204. ret.values[i] = enumValues[i];
  205. ret.resultTypes[i] = baselineValue == ret.values[i] ? BenchmarkResultType.NormalBaseline : BenchmarkResultType.Normal;
  206. }
  207. // External values
  208. for (int i = 0; i < externalCount; i++)
  209. {
  210. ret.names[enumCount + i] = externalFormats[i];
  211. ret.values[enumCount + i] = externalValues[i];
  212. ret.resultTypes[enumCount + i] = BenchmarkResultType.External;
  213. }
  214. // External baseline value if it exists
  215. if (externalBaselineFormat != null)
  216. {
  217. ret.names[variantCount - 1] = externalBaselineFormat;
  218. ret.values[variantCount - 1] = baselineValue;
  219. ret.resultTypes[variantCount - 1] = BenchmarkResultType.ExternalBaseline;
  220. }
  221. for (int i = 0; i < variantCount; i++)
  222. {
  223. if (nameOverride.TryGetValue(ret.values[i], out string name))
  224. ret.names[i] = string.Format(ret.names[i], name);
  225. else
  226. ret.names[i] = string.Format(ret.names[i], ret.defaultName);
  227. }
  228. if (new HashSet<int>(ret.values).Count != ret.values.Length)
  229. throw new ArgumentException($"Each enum value and external value in {benchmarkEnumType.Name} must be unique");
  230. return ret;
  231. }
  232. /// <summary>
  233. /// Reflects all possible arguments to a performance test method. Finds the parameter which benchmark comparisons
  234. /// are based around (must be an enum type decorated with [BenchmarkComparison] attribute).
  235. ///
  236. /// There is a (usually small) finite set of arguments possible in performance test methods due to
  237. /// requiring [Values(a, b, c)] attribute on any parameter that isn't a bool or enum.
  238. /// </summary>
  239. static void GatherAllArguments(ParameterInfo[] paramInfo, string methodName, BenchmarkComparisonTypeData structure, out int[] argCounts, out CustomAttributeTypedArgument[][] argValues, out string[] argNames, out int paramForComparison)
  240. {
  241. paramForComparison = -1;
  242. argCounts = new int[paramInfo.Length];
  243. argValues = new CustomAttributeTypedArgument[paramInfo.Length][];
  244. argNames = new string[paramInfo.Length];
  245. for (int p = 0; p < paramInfo.Length; p++)
  246. {
  247. // It is correct to throw if a parameter doesn't include Values attribute, NUnit errors as well
  248. CustomAttributeData valuesAttribute = null;
  249. foreach (var cad in paramInfo[p].GetCustomAttributesData())
  250. {
  251. if (cad.AttributeType == typeof(NUnit.Framework.ValuesAttribute))
  252. {
  253. valuesAttribute = cad;
  254. break;
  255. }
  256. }
  257. if (valuesAttribute == null)
  258. throw new ArgumentException($"No [Values(...)] attribute found for parameter {paramInfo[p].Name} in {methodName}");
  259. var values = valuesAttribute.ConstructorArguments;
  260. argNames[p] = paramInfo[p].Name;
  261. if (paramInfo[p].ParameterType.IsEnum && paramInfo[p].ParameterType.GetCustomAttribute<BenchmarkComparisonAttribute>() != null)
  262. {
  263. // [Values] <comparisonEnumType> <paramName>
  264. //
  265. // values.Count must be 0 or inconsistent benchmark measurements might be made.
  266. // Alternatively, we could treat as if it had no arguments for benchmarks, and allow performance testing for regressions
  267. // to be more specific, but for now it seems like a good idea to perf. test all valid combinations we offer, and in fact
  268. // a good idea to enforce that in some manner.
  269. if (paramInfo[p].ParameterType != structure.enumType)
  270. throw new ArgumentException($"The method {methodName} parameterizes benchmark comparison type {paramInfo[p].ParameterType.Name} but only supports {structure.enumType.Name}.");
  271. if (paramForComparison != -1)
  272. throw new ArgumentException($"More than one parameter specifies {structure.enumType.Name}. Only one may exist.");
  273. paramForComparison = p;
  274. argCounts[p] = structure.resultTypes.Length;
  275. argValues[p] = new CustomAttributeTypedArgument[argCounts[p]];
  276. // [Values(...)] <comparisonEnumType> <paramName>
  277. // This specifies comparison critera, and any excluded values will be shown as not available in the results report
  278. if (values.Count == 0)
  279. {
  280. // [Values]
  281. // This is the normal usage encompassing all comparison types
  282. for (int e = 0; e < argCounts[p]; e++)
  283. argValues[p][e] = new CustomAttributeTypedArgument(structure.values[e]);
  284. }
  285. else
  286. {
  287. // [Values(1-to-3-arguments)] <comparisonEnumType> <paramName>
  288. var ctorValues = values;
  289. if (values.Count == 1 && values[0].ArgumentType == typeof(object[]))
  290. {
  291. // [Values(more-than-3-arguments)] <comparisonEnumType> <paramName>
  292. //
  293. // This is for ValuesAttribute(params object[] args)
  294. var arrayValue = values[0].Value as System.Collections.Generic.IList<CustomAttributeTypedArgument>;
  295. ctorValues = arrayValue;
  296. }
  297. for (int e = 0; e < argCounts[p]; e++)
  298. {
  299. if (structure.resultTypes[e] == BenchmarkResultType.External || structure.resultTypes[e] == BenchmarkResultType.ExternalBaseline)
  300. argValues[p][e] = new CustomAttributeTypedArgument(structure.values[e]);
  301. else
  302. argValues[p][e] = default; // We can later check if ArgumentType is null to determine an unused comparison test
  303. }
  304. // If we don't include NormalBaseline values, it is an error - you can't not include a baseline
  305. bool hasNormalBaseline = false;
  306. string normalBaselineName = null;
  307. for (int i = 0; i < structure.resultTypes.Length; i++)
  308. {
  309. if (structure.resultTypes[i] == BenchmarkResultType.NormalBaseline)
  310. {
  311. hasNormalBaseline = true;
  312. normalBaselineName = structure.enumType.GetEnumNames()[i];
  313. }
  314. }
  315. bool specifiedBaseline = !hasNormalBaseline;
  316. for (int ca = 0; ca < ctorValues.Count; ca++)
  317. {
  318. // Ensure it's not some alternative value cast to the enum type such as an external baseline identifying value
  319. // because that would end up as part of the Performance Test Framework tests.
  320. if (ctorValues[ca].ArgumentType != structure.enumType)
  321. throw new ArgumentException($"Only {structure.enumType} values may be specified. External comparison types are always added automatically.");
  322. // Find the index this value would have been at, and set the argValue there to the struct.values for it
  323. for (int v = 0; v < structure.values.Length; v++)
  324. {
  325. if (structure.values[v] == (int)ctorValues[ca].Value)
  326. {
  327. argValues[p][v] = new CustomAttributeTypedArgument(structure.values[v]);
  328. if (structure.resultTypes[v] == BenchmarkResultType.NormalBaseline)
  329. specifiedBaseline = true;
  330. }
  331. }
  332. }
  333. if (!specifiedBaseline)
  334. throw new ArgumentException($"This comparison type requires the baseline {structure.enumType.Name}.{normalBaselineName} to be measured.");
  335. }
  336. }
  337. else if (values.Count == 0)
  338. {
  339. // [Values] <type> <paramName>
  340. //
  341. // This has default behaviour for bools and enums, otherwise error
  342. if (paramInfo[p].ParameterType == typeof(bool))
  343. {
  344. argCounts[p] = 2;
  345. argValues[p] = new CustomAttributeTypedArgument[]
  346. {
  347. new CustomAttributeTypedArgument(true),
  348. new CustomAttributeTypedArgument(false)
  349. };
  350. }
  351. else if (paramInfo[p].ParameterType.IsEnum)
  352. {
  353. var enumValues = Enum.GetValues(paramInfo[p].ParameterType);
  354. argCounts[p] = enumValues.Length;
  355. argValues[p] = new CustomAttributeTypedArgument[argCounts[p]];
  356. for (int e = 0; e < argCounts[p]; e++)
  357. argValues[p][e] = new CustomAttributeTypedArgument(enumValues.GetValue(e));
  358. }
  359. else
  360. throw new ArgumentException($"[Values] attribute of parameter {paramInfo[p].Name} in {methodName} is empty");
  361. }
  362. else if (values.Count == 1 && values[0].ArgumentType == typeof(object[]))
  363. {
  364. // [Values(more-than-3-arguments)] <type> <paramName>
  365. //
  366. // This is for ValuesAttribute(params object[] args)
  367. var arrayValue = values[0].Value as System.Collections.Generic.IList<CustomAttributeTypedArgument>;
  368. argValues[p] = new CustomAttributeTypedArgument[arrayValue.Count];
  369. arrayValue.CopyTo(argValues[p], 0);
  370. argCounts[p] = arrayValue.Count;
  371. }
  372. else
  373. {
  374. // [Values(1-to-3-arguments)] <type> <paramName>
  375. argValues[p] = new CustomAttributeTypedArgument[values.Count];
  376. values.CopyTo(argValues[p], 0);
  377. argCounts[p] = values.Count;
  378. }
  379. }
  380. if (paramForComparison == -1)
  381. throw new ArgumentException($"No benchmark comparison is parameterized. One must be specified");
  382. }
  383. /// <summary>
  384. /// Given
  385. /// a) X number of permutations for all arguments to each parameter in a performance test method
  386. /// b) the possible arguments to each parameter
  387. /// c) the parameter defining the benchmark comparison
  388. ///
  389. /// Return
  390. /// a) the argument set (called variant) for Permutation[0 to X-1]
  391. /// b) the isolated benchmark comparison index, based on the benchmark comparison enum values, for this variant
  392. /// </summary>
  393. static BenchmarkResultType GetVariantArguments(int variantIndex, BenchmarkComparisonTypeData structure, int paramForComparison, CustomAttributeTypedArgument[][] argValues, int[] argCounts, out object[] args, out int comparisonIndex)
  394. {
  395. comparisonIndex = 0;
  396. int numParams = argValues.Length;
  397. // Calculate ValuesAttribute indices for each parameter
  398. // Calculate actual comparison index to ensure only benchmarks comparison are bunched together
  399. int[] argValueIndex = new int[numParams];
  400. for (int p = 0, argSet = variantIndex, comparisonMult = 1; p < numParams; p++)
  401. {
  402. argValueIndex[p] = argSet % argCounts[p];
  403. argSet = (argSet - argValueIndex[p]) / argCounts[p];
  404. if (p != paramForComparison)
  405. {
  406. comparisonIndex += argValueIndex[p] * comparisonMult;
  407. comparisonMult *= argCounts[p];
  408. }
  409. }
  410. // Find each argument using above ValuesAttribute indices
  411. args = new object[numParams];
  412. if (argValues[paramForComparison][argValueIndex[paramForComparison]].ArgumentType == null)
  413. return BenchmarkResultType.Ignored;
  414. for (int p = 0; p < numParams; p++)
  415. args[p] = argValues[p][argValueIndex[p]].Value;
  416. return structure.resultTypes[argValueIndex[paramForComparison]];
  417. }
  418. /// <summary>
  419. /// Runs benchmarking for all defined benchmark methods in a type.
  420. /// </summary>
  421. static BenchmarkReportGroup GatherGroupData(Type t, BenchmarkComparisonTypeData structure)
  422. {
  423. var group = new BenchmarkReportGroup(structure.defaultName, structure.names, structure.resultTypes, structure.resultDecimalPlaces);
  424. uint groupFootnoteBit = BenchmarkResults.kFlagFootnotes;
  425. var allMethods = t.GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
  426. var methods = new List<MethodInfo>(allMethods.Length);
  427. foreach (var m in allMethods)
  428. {
  429. if (m.GetCustomAttribute<NUnit.Framework.TestAttribute>() != null && m.GetCustomAttribute<PerformanceAttribute>() != null)
  430. methods.Add(m);
  431. }
  432. var inst = Activator.CreateInstance(t);
  433. for (int m = 0; m < methods.Count; m++)
  434. {
  435. var method = methods[m];
  436. // Get ValueAttributes information for all parameters
  437. GatherAllArguments(method.GetParameters(), $"{t.Name}.{method.Name}", structure,
  438. out var argCounts, out var argValues, out var argNames, out var paramForComparison);
  439. // Record any footnotes for this method
  440. uint comparisonFootnoteFlags = 0;
  441. foreach (var cad in method.GetCustomAttributesData())
  442. {
  443. if (cad.AttributeType != typeof(BenchmarkTestFootnoteAttribute))
  444. continue;
  445. var footnoteText = new NativeText($"{method.Name}(", Allocator.Persistent);
  446. int paramsShown = 0;
  447. for (int p = 0; p < argNames.Length; p++)
  448. {
  449. if (p == paramForComparison)
  450. continue;
  451. if (paramsShown++ > 0)
  452. footnoteText.Append(", ");
  453. footnoteText.Append(argNames[p]);
  454. }
  455. footnoteText.Append(")");
  456. if (cad.ConstructorArguments.Count == 1)
  457. footnoteText.Append($" -- {(string)cad.ConstructorArguments[0].Value}");
  458. group.customFootnotes.Add(groupFootnoteBit, footnoteText);
  459. comparisonFootnoteFlags |= groupFootnoteBit;
  460. groupFootnoteBit <<= 1;
  461. }
  462. // Calculate number of variations based on all ValuesAttributes + parameters
  463. int totalVariants = 1;
  464. for (int p = 0; p < argCounts.Length; p++)
  465. totalVariants *= argCounts[p];
  466. int numComparisons = totalVariants / argCounts[paramForComparison];
  467. BenchmarkReportComparison[] comparison = new BenchmarkReportComparison[numComparisons];
  468. for (int i = 0; i < totalVariants; i++)
  469. {
  470. SetProgressText($"Running benchmark {i + 1}/{totalVariants} for {method.Name}", (float)(m + 1) / methods.Count);
  471. // comparisonIndex indicates the variation of a complete benchmark comparison. i.e.
  472. // you could be benchmarking between 3 different variants (such as NativeArray vs UnsafeArray vs C# Array)
  473. // but you may also have 4 versions of that (such as 1000 elements, 10000 elements, 100000, and 1000000)
  474. BenchmarkResultType resultType = GetVariantArguments(i, structure, paramForComparison, argValues, argCounts,
  475. out var args, out int comparisonIndex);
  476. if (resultType == BenchmarkResultType.Ignored)
  477. {
  478. if (comparison[comparisonIndex].comparisonName.IsEmpty)
  479. comparison[comparisonIndex] = new BenchmarkReportComparison(method.Name);
  480. comparison[comparisonIndex].results.Add(BenchmarkResults.Ignored);
  481. continue;
  482. }
  483. if (comparison[comparisonIndex].comparisonName.IsEmpty)
  484. {
  485. string paramsString = null;
  486. for (int p = 0; p < argCounts.Length; p++)
  487. {
  488. if (p == paramForComparison)
  489. continue;
  490. if (paramsString == null)
  491. paramsString = $"({args[p]}";
  492. else
  493. paramsString += $", {args[p]}";
  494. }
  495. if (paramsString != null)
  496. comparison[comparisonIndex] = new BenchmarkReportComparison($"{method.Name}{paramsString})");
  497. else
  498. comparison[comparisonIndex] = new BenchmarkReportComparison(method.Name);
  499. }
  500. // Call the performance method
  501. method.Invoke(inst, args);
  502. var results = BenchmarkMeasure.CalculateLastResults(structure.resultUnit, structure.resultStatistic);
  503. comparison[comparisonIndex].results.Add(results);
  504. }
  505. // Add all sets of comparisons to the full group
  506. for (int i = 0; i < numComparisons; i++)
  507. {
  508. comparison[i].footnoteFlags |= comparisonFootnoteFlags;
  509. comparison[i].RankResults(structure.resultTypes);
  510. group.comparisons.Add(comparison[i]);
  511. }
  512. }
  513. return group;
  514. }
  515. /// <summary>
  516. /// Runs benchmarking for all given types.
  517. /// </summary>
  518. /// <param name="title">The title to the full report</param>
  519. /// <param name="benchmarkTypes">An array of types each marked with <see cref="BenchmarkAttribute"/></param>
  520. /// <returns></returns>
  521. public static BenchmarkReports RunBenchmarks(string title, Type[] benchmarkTypes)
  522. {
  523. BenchmarkMeasure.ForBenchmarks = true;
  524. BenchmarkReports reports = default;
  525. try
  526. {
  527. reports = new BenchmarkReports(title);
  528. for (int i = 0; i < benchmarkTypes.Length; i++)
  529. {
  530. StartProgress(title, i, benchmarkTypes.Length, benchmarkTypes[i].Name);
  531. SetProgressText("Gathering benchmark data", 0);
  532. var benchmarkStructure = GatherComparisonStructure(benchmarkTypes[i]);
  533. var group = GatherGroupData(benchmarkTypes[i], benchmarkStructure);
  534. reports.groups.Add(group);
  535. }
  536. }
  537. finally
  538. {
  539. BenchmarkMeasure.ForBenchmarks = false;
  540. EndProgress();
  541. }
  542. return reports;
  543. }
  544. }
  545. }