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