com.wallstop-studios.unity-helpers 2.1.0 → 2.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (106) hide show
  1. package/AGENTS.md +1 -0
  2. package/Docs/ILIST_SORTING_PERFORMANCE.md +92 -0
  3. package/{package-lock.json.meta → Docs/ILIST_SORTING_PERFORMANCE.md.meta} +1 -1
  4. package/Docs/INDEX.md +11 -1
  5. package/Docs/Images/random_generators.svg +7 -7
  6. package/Docs/RANDOM_PERFORMANCE.md +17 -14
  7. package/Docs/REFLECTION_HELPERS.md +84 -1
  8. package/Docs/REFLECTION_PERFORMANCE.md +169 -0
  9. package/Docs/REFLECTION_PERFORMANCE.md.meta +7 -0
  10. package/Docs/RELATIONAL_COMPONENTS.md +6 -0
  11. package/Docs/RELATIONAL_COMPONENT_PERFORMANCE.md +63 -0
  12. package/Docs/RELATIONAL_COMPONENT_PERFORMANCE.md.meta +7 -0
  13. package/Docs/SPATIAL_TREE_2D_PERFORMANCE.md +64 -64
  14. package/Docs/SPATIAL_TREE_3D_PERFORMANCE.md +64 -64
  15. package/Editor/Core/Helper/AnimationEventHelpers.cs +1 -1
  16. package/Editor/Sprites/AnimationCopier.cs +1 -1
  17. package/Editor/Sprites/AnimationViewerWindow.cs +4 -4
  18. package/Editor/Sprites/SpriteSettingsApplierAPI.cs +2 -1
  19. package/Editor/Sprites/TextureResizerWizard.cs +4 -3
  20. package/Editor/Utils/ScriptableObjectSingletonCreator.cs +3 -3
  21. package/README.md +33 -18
  22. package/Runtime/Core/Attributes/BaseRelationalComponentAttribute.cs +147 -20
  23. package/Runtime/Core/Attributes/ChildComponentAttribute.cs +630 -117
  24. package/Runtime/Core/Attributes/NotNullAttribute.cs +5 -2
  25. package/Runtime/Core/Attributes/ParentComponentAttribute.cs +477 -103
  26. package/Runtime/Core/Attributes/RelationalComponentAssigner.cs +26 -3
  27. package/Runtime/Core/Attributes/RelationalComponentExtensions.cs +19 -3
  28. package/Runtime/Core/Attributes/SiblingComponentAttribute.cs +265 -92
  29. package/Runtime/Core/CodeGen.meta +8 -0
  30. package/Runtime/Core/DataStructure/ImmutableBitSet.cs +5 -20
  31. package/Runtime/Core/Extension/IListExtensions.cs +720 -12
  32. package/Runtime/Core/Helper/Logging/UnityLogTagFormatter.cs +11 -7
  33. package/Runtime/Core/Helper/Objects.cs +1 -1
  34. package/Runtime/Core/Helper/ReflectionHelpers.Factory.cs +5142 -0
  35. package/Runtime/Core/Helper/ReflectionHelpers.Factory.cs.meta +11 -0
  36. package/Runtime/Core/Helper/ReflectionHelpers.cs +1812 -1518
  37. package/Runtime/Core/Helper/UnityMainThreadDispatcher.cs +2 -3
  38. package/Runtime/Core/Math/Line2D.cs +2 -4
  39. package/Runtime/Core/Math/Line3D.cs +2 -4
  40. package/Runtime/Core/Random/AbstractRandom.cs +52 -5
  41. package/Runtime/Core/Random/DotNetRandom.cs +3 -3
  42. package/Runtime/Core/Random/FlurryBurstRandom.cs +279 -0
  43. package/Runtime/Core/Random/FlurryBurstRandom.cs.meta +3 -0
  44. package/Runtime/Core/Random/IllusionFlow.cs +3 -3
  45. package/Runtime/Core/Random/LinearCongruentialGenerator.cs +3 -3
  46. package/Runtime/Core/Random/PcgRandom.cs +6 -6
  47. package/Runtime/Core/Random/PhotonSpinRandom.cs +387 -0
  48. package/Runtime/Core/Random/PhotonSpinRandom.cs.meta +3 -0
  49. package/Runtime/Core/Random/RomuDuo.cs +3 -3
  50. package/Runtime/Core/Random/SplitMix64.cs +3 -3
  51. package/Runtime/Core/Random/SquirrelRandom.cs +6 -4
  52. package/Runtime/Core/Random/StormDropRandom.cs +271 -0
  53. package/Runtime/Core/Random/StormDropRandom.cs.meta +3 -0
  54. package/Runtime/Core/Random/UnityRandom.cs +3 -3
  55. package/Runtime/Core/Random/WyRandom.cs +6 -4
  56. package/Runtime/Core/Random/XorShiftRandom.cs +3 -3
  57. package/Runtime/Core/Random/XoroShiroRandom.cs +3 -3
  58. package/Runtime/Tags/AttributeMetadataCache.cs +316 -9
  59. package/Runtime/Tags/CosmeticEffectData.cs +1 -1
  60. package/Runtime/Visuals/UIToolkit/MultiFileSelectorElement.cs +3 -3
  61. package/Tests/Editor/Helper/HelpersTests.cs +2 -2
  62. package/Tests/Editor/Helper/ReflectionHelpersTypedEditorTests.cs +87 -0
  63. package/Tests/Editor/Helper/ReflectionHelpersTypedEditorTests.cs.meta +11 -0
  64. package/Tests/Editor/Helper/SpriteHelpersTests.cs +1 -1
  65. package/Tests/Editor/PrefabCheckerReportTests.cs +3 -3
  66. package/Tests/Editor/Sprites/AnimationCopierFilterTests.cs +18 -12
  67. package/Tests/Editor/Sprites/AnimationCopierWindowTests.cs +8 -7
  68. package/Tests/Editor/Sprites/AnimationViewerWindowTests.cs +2 -1
  69. package/Tests/Editor/Sprites/ScriptableSpriteAtlasEditorTests.cs +6 -5
  70. package/Tests/Editor/Sprites/SpriteCropperAdditionalTests.cs +2 -1
  71. package/Tests/Editor/Sprites/SpriteCropperTests.cs +7 -6
  72. package/Tests/Editor/Sprites/SpritePivotAdjusterAdditionalTests.cs +2 -1
  73. package/Tests/Editor/Sprites/SpritePivotAdjusterTests.cs +4 -3
  74. package/Tests/Editor/Sprites/TextureResizerWizardTests.cs +10 -9
  75. package/Tests/Editor/Sprites/TextureSettingsApplierAPITests.cs +2 -1
  76. package/Tests/Editor/Tags/AttributeMetadataCacheTests.cs +192 -0
  77. package/Tests/Editor/Tags/AttributeMetadataCacheTests.cs.meta +11 -0
  78. package/Tests/Editor/Tags.meta +8 -0
  79. package/Tests/Runtime/Extensions/IListExtensionTests.cs +187 -1
  80. package/Tests/Runtime/Helper/ObjectsTests.cs +4 -4
  81. package/Tests/Runtime/Helper/ReflectionHelperCapabilityMatrixTests.cs +2923 -0
  82. package/Tests/Runtime/Helper/ReflectionHelperCapabilityMatrixTests.cs.meta +11 -0
  83. package/Tests/Runtime/Helper/ReflectionHelperTests.cs +660 -0
  84. package/Tests/Runtime/Integrations/Reflex/RelationalComponentsReflexTests.cs +2 -2
  85. package/Tests/Runtime/Performance/IListSortingPerformanceTests.cs +346 -0
  86. package/Tests/Runtime/Performance/IListSortingPerformanceTests.cs.meta +11 -0
  87. package/Tests/Runtime/Performance/RandomPerformanceTests.cs +3 -0
  88. package/Tests/Runtime/Performance/ReflectionPerformanceTests.cs +1238 -0
  89. package/Tests/Runtime/Performance/ReflectionPerformanceTests.cs.meta +11 -0
  90. package/Tests/Runtime/Performance/RelationalComponentBenchmarkTests.cs +832 -0
  91. package/Tests/Runtime/Performance/RelationalComponentBenchmarkTests.cs.meta +11 -0
  92. package/Tests/Runtime/Random/FlurryBurstRandomTests.cs +12 -0
  93. package/Tests/Runtime/Random/FlurryBurstRandomTests.cs.meta +3 -0
  94. package/Tests/Runtime/Random/PhotonSpinRandomTests.cs +12 -0
  95. package/Tests/Runtime/Random/PhotonSpinRandomTests.cs.meta +3 -0
  96. package/Tests/Runtime/Random/RandomProtoSerializationTests.cs +14 -0
  97. package/Tests/Runtime/Random/RandomTestBase.cs +39 -4
  98. package/Tests/Runtime/Random/StormDropRandomTests.cs +12 -0
  99. package/Tests/Runtime/Random/StormDropRandomTests.cs.meta +3 -0
  100. package/Tests/Runtime/Serialization/ProtoInterfaceResolutionEdgeTests.cs +2 -2
  101. package/Tests/Runtime/Serialization/ProtoRootRegistrationTests.cs +1 -1
  102. package/Tests/Runtime/Serialization/ProtoSerializeBehaviorTests.cs +1 -1
  103. package/Tests/Runtime/Tags/PeriodicEffectDefinitionSerializationTests.cs +2 -2
  104. package/package.json +1 -1
  105. package/Tests/Runtime/Performance/RelationComponentPerformanceTests.cs +0 -60
  106. package/Tests/Runtime/Performance/RelationComponentPerformanceTests.cs.meta +0 -3
@@ -0,0 +1,1238 @@
1
+ namespace WallstopStudios.UnityHelpers.Tests.Performance
2
+ {
3
+ using System;
4
+ using System.Collections.Generic;
5
+ using System.Diagnostics;
6
+ using System.Reflection;
7
+ using System.Runtime.InteropServices;
8
+ using NUnit.Framework;
9
+ using WallstopStudios.UnityHelpers.Core.Helper;
10
+
11
+ public sealed class ReflectionPerformanceTests
12
+ {
13
+ private const int BatchSize = 256;
14
+ private static readonly TimeSpan BenchmarkDuration = TimeSpan.FromMilliseconds(250);
15
+ private static int sink;
16
+
17
+ [Test]
18
+ [Timeout(0)]
19
+ public void Benchmark()
20
+ {
21
+ StrategyConfig[] strategies =
22
+ {
23
+ new StrategyConfig("Default (auto)", null, null),
24
+ new StrategyConfig("Expressions", true, false),
25
+ new StrategyConfig("Dynamic IL", false, true),
26
+ new StrategyConfig("Reflection Fallback", false, false),
27
+ };
28
+
29
+ List<StrategyRunResult> supportedRuns = new List<StrategyRunResult>();
30
+
31
+ foreach (StrategyConfig config in strategies)
32
+ {
33
+ StrategyRunResult result = RunStrategy(config);
34
+ if (!result.Supported)
35
+ {
36
+ UnityEngine.Debug.LogWarning(
37
+ $"[ReflectionPerf] Skipping {config.Label}: {result.SkipReason}"
38
+ );
39
+ continue;
40
+ }
41
+
42
+ supportedRuns.Add(result);
43
+ }
44
+
45
+ List<string> outputLines = new List<string>
46
+ {
47
+ string.Format(
48
+ System.Globalization.CultureInfo.InvariantCulture,
49
+ "Generated on {0:yyyy-MM-dd HH:mm:ss} UTC",
50
+ DateTime.UtcNow
51
+ ),
52
+ string.Empty,
53
+ };
54
+
55
+ foreach (StrategyRunResult run in supportedRuns)
56
+ {
57
+ outputLines.Add($"### Strategy: {run.Label}");
58
+ outputLines.Add(string.Empty);
59
+ outputLines.Add("#### Boxed Access (object)");
60
+ outputLines.Add(string.Empty);
61
+ outputLines.Add(
62
+ "| Scenario | Helper (ops/sec) | System.Reflection (ops/sec) | Speedup vs Reflection |"
63
+ );
64
+ outputLines.Add(
65
+ "| -------- | ---------------- | --------------------------- | --------------------- |"
66
+ );
67
+
68
+ foreach (ScenarioResult result in run.BoxedResults)
69
+ {
70
+ string row = string.Format(
71
+ System.Globalization.CultureInfo.InvariantCulture,
72
+ "| {0} | {1} | {2} | {3:F2}x |",
73
+ result.Name,
74
+ FormatOps(result.HelperOpsPerSecond),
75
+ FormatOps(result.BaselineOpsPerSecond),
76
+ result.Speedup
77
+ );
78
+ outputLines.Add(row);
79
+ UnityEngine.Debug.Log(
80
+ string.Format(
81
+ System.Globalization.CultureInfo.InvariantCulture,
82
+ "[ReflectionPerf][{0}][Boxed] {1}: helpers={2:N0} ops/s, reflection={3:N0} ops/s",
83
+ run.Label,
84
+ result.Name,
85
+ result.HelperOpsPerSecond,
86
+ result.BaselineOpsPerSecond
87
+ )
88
+ );
89
+ }
90
+
91
+ outputLines.Add(string.Empty);
92
+ outputLines.Add("#### Typed Access (no boxing)");
93
+ outputLines.Add(string.Empty);
94
+ outputLines.Add(
95
+ "| Scenario | Helper (ops/sec) | Baseline Delegate (ops/sec) | System.Reflection (ops/sec) | Speedup vs Delegate | Speedup vs Reflection |"
96
+ );
97
+ outputLines.Add(
98
+ "| -------- | ---------------- | --------------------------- | --------------------------- | ------------------- | -------------------- |"
99
+ );
100
+
101
+ Dictionary<string, double> reflectionBaselineLookup = new Dictionary<
102
+ string,
103
+ double
104
+ >(StringComparer.Ordinal);
105
+ foreach (ScenarioResult boxed in run.BoxedResults)
106
+ {
107
+ reflectionBaselineLookup[GetScenarioKey(boxed.Name)] =
108
+ boxed.BaselineOpsPerSecond;
109
+ }
110
+
111
+ foreach (ScenarioResult result in run.TypedResults)
112
+ {
113
+ double reflectionOps = reflectionBaselineLookup.TryGetValue(
114
+ GetScenarioKey(result.Name),
115
+ out double value
116
+ )
117
+ ? value
118
+ : double.NaN;
119
+ double speedupVsReflection =
120
+ reflectionOps <= 0.0
121
+ ? double.PositiveInfinity
122
+ : result.HelperOpsPerSecond / reflectionOps;
123
+
124
+ string row = string.Format(
125
+ System.Globalization.CultureInfo.InvariantCulture,
126
+ "| {0} | {1} | {2} | {3} | {4:F2}x | {5:F2}x |",
127
+ result.Name,
128
+ FormatOps(result.HelperOpsPerSecond),
129
+ FormatOps(result.BaselineOpsPerSecond),
130
+ FormatOps(reflectionOps),
131
+ result.Speedup,
132
+ speedupVsReflection
133
+ );
134
+ outputLines.Add(row);
135
+ UnityEngine.Debug.Log(
136
+ string.Format(
137
+ System.Globalization.CultureInfo.InvariantCulture,
138
+ "[ReflectionPerf][{0}][Typed] {1}: helpers={2:N0} ops/s, delegate={3:N0} ops/s, reflection={4:N0} ops/s",
139
+ run.Label,
140
+ result.Name,
141
+ result.HelperOpsPerSecond,
142
+ result.BaselineOpsPerSecond,
143
+ reflectionOps
144
+ )
145
+ );
146
+ }
147
+
148
+ outputLines.Add(string.Empty);
149
+ }
150
+
151
+ string token = string.Format(
152
+ System.Globalization.CultureInfo.InvariantCulture,
153
+ "REFLECTION_PERFORMANCE_{0}",
154
+ GetOsToken()
155
+ );
156
+
157
+ BenchmarkReadmeUpdater.UpdateSection(
158
+ token,
159
+ outputLines,
160
+ "Docs/REFLECTION_PERFORMANCE.md"
161
+ );
162
+ }
163
+
164
+ private static ReflectionPerfTarget CreateTargetInstance()
165
+ {
166
+ ReflectionPerfTarget instance = new ReflectionPerfTarget
167
+ {
168
+ InstanceField = 5,
169
+ InstanceProperty = 7,
170
+ };
171
+ ReflectionPerfTarget.StaticField = 11;
172
+ ReflectionPerfTarget.StaticProperty = 13;
173
+ return instance;
174
+ }
175
+
176
+ private static StrategyRunResult RunStrategy(StrategyConfig config)
177
+ {
178
+ IDisposable capabilityOverride = null;
179
+
180
+ try
181
+ {
182
+ if (config.RequiresOverride)
183
+ {
184
+ capabilityOverride = ReflectionHelpers.OverrideReflectionCapabilities(
185
+ config.ExpressionsOverride,
186
+ config.DynamicIlOverride
187
+ );
188
+ }
189
+
190
+ if (config.ExpressionsOverride == true && !ReflectionHelpers.ExpressionsEnabled)
191
+ {
192
+ return StrategyRunResult.CreateUnsupported(
193
+ config.Label,
194
+ "Expression compilation is not supported on this runtime."
195
+ );
196
+ }
197
+
198
+ if (config.DynamicIlOverride == true && !ReflectionHelpers.DynamicIlEnabled)
199
+ {
200
+ return StrategyRunResult.CreateUnsupported(
201
+ config.Label,
202
+ "Dynamic IL emission is not supported on this runtime."
203
+ );
204
+ }
205
+
206
+ List<ScenarioResult> boxed = RunScenarios(
207
+ CreateBoxedScenarios(CreateTargetInstance())
208
+ );
209
+ List<ScenarioResult> typed = RunScenarios(
210
+ CreateTypedScenarios(CreateTargetInstance())
211
+ );
212
+
213
+ return StrategyRunResult.Create(config.Label, boxed, typed);
214
+ }
215
+ finally
216
+ {
217
+ capabilityOverride?.Dispose();
218
+ }
219
+ }
220
+
221
+ private static List<ScenarioResult> RunScenarios(IEnumerable<Scenario> scenarios)
222
+ {
223
+ List<ScenarioResult> results = new List<ScenarioResult>();
224
+ foreach (Scenario scenario in scenarios)
225
+ {
226
+ results.Add(RunScenario(scenario));
227
+ }
228
+
229
+ return results;
230
+ }
231
+
232
+ private static IEnumerable<Scenario> CreateBoxedScenarios(ReflectionPerfTarget instance)
233
+ {
234
+ Type targetType = typeof(ReflectionPerfTarget);
235
+ FieldInfo instanceField = targetType.GetField(
236
+ nameof(ReflectionPerfTarget.InstanceField)
237
+ );
238
+ FieldInfo staticField = targetType.GetField(nameof(ReflectionPerfTarget.StaticField));
239
+ PropertyInfo instanceProperty = targetType.GetProperty(
240
+ nameof(ReflectionPerfTarget.InstanceProperty)
241
+ );
242
+ PropertyInfo staticProperty = targetType.GetProperty(
243
+ nameof(ReflectionPerfTarget.StaticProperty)
244
+ );
245
+ MethodInfo instanceMethod = targetType.GetMethod(nameof(ReflectionPerfTarget.Combine));
246
+ MethodInfo staticMethod = targetType.GetMethod(
247
+ nameof(ReflectionPerfTarget.StaticCombine)
248
+ );
249
+ ConstructorInfo constructor = targetType.GetConstructor(new[] { typeof(int) });
250
+
251
+ if (
252
+ instanceField == null
253
+ || staticField == null
254
+ || instanceProperty == null
255
+ || staticProperty == null
256
+ || instanceMethod == null
257
+ || staticMethod == null
258
+ || constructor == null
259
+ )
260
+ {
261
+ throw new InvalidOperationException("ReflectionPerfTarget members not found.");
262
+ }
263
+
264
+ Func<object, object> instanceFieldGetter = ReflectionHelpers.GetFieldGetter(
265
+ instanceField
266
+ );
267
+ Action<object, object> instanceFieldSetter = ReflectionHelpers.GetFieldSetter(
268
+ instanceField
269
+ );
270
+ Func<object> staticFieldGetter = ReflectionHelpers.GetStaticFieldGetter(staticField);
271
+ Action<object> staticFieldSetter = ReflectionHelpers.GetStaticFieldSetter(staticField);
272
+ Func<object, object> instancePropertyGetter = ReflectionHelpers.GetPropertyGetter(
273
+ instanceProperty
274
+ );
275
+ Action<object, object> instancePropertySetter = ReflectionHelpers.GetPropertySetter(
276
+ instanceProperty
277
+ );
278
+ Func<object, object> staticPropertyGetter = ReflectionHelpers.GetPropertyGetter(
279
+ staticProperty
280
+ );
281
+ Action<object, object> staticPropertySetter = ReflectionHelpers.GetPropertySetter(
282
+ staticProperty
283
+ );
284
+ Func<object, object[], object> instanceMethodInvoker =
285
+ ReflectionHelpers.GetMethodInvoker(instanceMethod);
286
+ Func<object[], object> staticMethodInvoker = ReflectionHelpers.GetStaticMethodInvoker(
287
+ staticMethod
288
+ );
289
+ Func<object[], object> constructorInvoker = ReflectionHelpers.GetConstructor(
290
+ constructor
291
+ );
292
+
293
+ yield return Scenario.Create(
294
+ "Instance Field Get (boxed)",
295
+ () =>
296
+ {
297
+ int count = 0;
298
+ for (int i = 0; i < BatchSize; i++)
299
+ {
300
+ sink ^= (int)instanceField.GetValue(instance);
301
+ count++;
302
+ }
303
+
304
+ return count;
305
+ },
306
+ () =>
307
+ {
308
+ int count = 0;
309
+ for (int i = 0; i < BatchSize; i++)
310
+ {
311
+ sink ^= (int)instanceFieldGetter(instance);
312
+ count++;
313
+ }
314
+
315
+ return count;
316
+ }
317
+ );
318
+
319
+ yield return Scenario.Create(
320
+ "Instance Field Set (boxed)",
321
+ () =>
322
+ {
323
+ int count = 0;
324
+ int value = 0;
325
+ for (int i = 0; i < BatchSize; i++)
326
+ {
327
+ instanceField.SetValue(instance, value);
328
+ sink ^= instance.InstanceField;
329
+ value++;
330
+ count++;
331
+ }
332
+
333
+ return count;
334
+ },
335
+ () =>
336
+ {
337
+ int count = 0;
338
+ int value = 0;
339
+ for (int i = 0; i < BatchSize; i++)
340
+ {
341
+ instanceFieldSetter(instance, value);
342
+ sink ^= instance.InstanceField;
343
+ value++;
344
+ count++;
345
+ }
346
+
347
+ return count;
348
+ }
349
+ );
350
+
351
+ yield return Scenario.Create(
352
+ "Static Field Get (boxed)",
353
+ () =>
354
+ {
355
+ int count = 0;
356
+ for (int i = 0; i < BatchSize; i++)
357
+ {
358
+ sink ^= (int)staticField.GetValue(null);
359
+ count++;
360
+ }
361
+
362
+ return count;
363
+ },
364
+ () =>
365
+ {
366
+ int count = 0;
367
+ for (int i = 0; i < BatchSize; i++)
368
+ {
369
+ sink ^= (int)staticFieldGetter();
370
+ count++;
371
+ }
372
+
373
+ return count;
374
+ }
375
+ );
376
+
377
+ yield return Scenario.Create(
378
+ "Static Field Set (boxed)",
379
+ () =>
380
+ {
381
+ int count = 0;
382
+ int value = 0;
383
+ for (int i = 0; i < BatchSize; i++)
384
+ {
385
+ staticField.SetValue(null, value);
386
+ sink ^= ReflectionPerfTarget.StaticField;
387
+ value++;
388
+ count++;
389
+ }
390
+
391
+ return count;
392
+ },
393
+ () =>
394
+ {
395
+ int count = 0;
396
+ int value = 0;
397
+ for (int i = 0; i < BatchSize; i++)
398
+ {
399
+ staticFieldSetter(value);
400
+ sink ^= ReflectionPerfTarget.StaticField;
401
+ value++;
402
+ count++;
403
+ }
404
+
405
+ return count;
406
+ }
407
+ );
408
+
409
+ yield return Scenario.Create(
410
+ "Instance Property Get (boxed)",
411
+ () =>
412
+ {
413
+ int count = 0;
414
+ for (int i = 0; i < BatchSize; i++)
415
+ {
416
+ sink ^= (int)instanceProperty.GetValue(instance);
417
+ count++;
418
+ }
419
+
420
+ return count;
421
+ },
422
+ () =>
423
+ {
424
+ int count = 0;
425
+ for (int i = 0; i < BatchSize; i++)
426
+ {
427
+ sink ^= (int)instancePropertyGetter(instance);
428
+ count++;
429
+ }
430
+
431
+ return count;
432
+ }
433
+ );
434
+
435
+ yield return Scenario.Create(
436
+ "Instance Property Set (boxed)",
437
+ () =>
438
+ {
439
+ int count = 0;
440
+ int value = 0;
441
+ for (int i = 0; i < BatchSize; i++)
442
+ {
443
+ instanceProperty.SetValue(instance, value);
444
+ sink ^= instance.InstanceProperty;
445
+ value++;
446
+ count++;
447
+ }
448
+
449
+ return count;
450
+ },
451
+ () =>
452
+ {
453
+ int count = 0;
454
+ int value = 0;
455
+ for (int i = 0; i < BatchSize; i++)
456
+ {
457
+ instancePropertySetter(instance, value);
458
+ sink ^= instance.InstanceProperty;
459
+ value++;
460
+ count++;
461
+ }
462
+
463
+ return count;
464
+ }
465
+ );
466
+
467
+ yield return Scenario.Create(
468
+ "Static Property Get (boxed)",
469
+ () =>
470
+ {
471
+ int count = 0;
472
+ for (int i = 0; i < BatchSize; i++)
473
+ {
474
+ sink ^= (int)staticProperty.GetValue(null);
475
+ count++;
476
+ }
477
+
478
+ return count;
479
+ },
480
+ () =>
481
+ {
482
+ int count = 0;
483
+ for (int i = 0; i < BatchSize; i++)
484
+ {
485
+ sink ^= (int)staticPropertyGetter(null);
486
+ count++;
487
+ }
488
+
489
+ return count;
490
+ }
491
+ );
492
+
493
+ yield return Scenario.Create(
494
+ "Static Property Set (boxed)",
495
+ () =>
496
+ {
497
+ int count = 0;
498
+ int value = 0;
499
+ for (int i = 0; i < BatchSize; i++)
500
+ {
501
+ staticProperty.SetValue(null, value);
502
+ sink ^= ReflectionPerfTarget.StaticProperty;
503
+ value++;
504
+ count++;
505
+ }
506
+
507
+ return count;
508
+ },
509
+ () =>
510
+ {
511
+ int count = 0;
512
+ int value = 0;
513
+ for (int i = 0; i < BatchSize; i++)
514
+ {
515
+ staticPropertySetter(null, value);
516
+ sink ^= ReflectionPerfTarget.StaticProperty;
517
+ value++;
518
+ count++;
519
+ }
520
+
521
+ return count;
522
+ }
523
+ );
524
+
525
+ yield return Scenario.Create(
526
+ "Instance Method Invoke (boxed)",
527
+ () =>
528
+ {
529
+ int count = 0;
530
+ object[] arguments = { 3, 5 };
531
+ for (int i = 0; i < BatchSize; i++)
532
+ {
533
+ sink ^= (int)instanceMethod.Invoke(instance, arguments);
534
+ count++;
535
+ }
536
+
537
+ return count;
538
+ },
539
+ () =>
540
+ {
541
+ int count = 0;
542
+ object[] arguments = { 3, 5 };
543
+ for (int i = 0; i < BatchSize; i++)
544
+ {
545
+ sink ^= (int)instanceMethodInvoker(instance, arguments);
546
+ count++;
547
+ }
548
+
549
+ return count;
550
+ }
551
+ );
552
+
553
+ yield return Scenario.Create(
554
+ "Static Method Invoke (boxed)",
555
+ () =>
556
+ {
557
+ int count = 0;
558
+ object[] arguments = { 3, 5 };
559
+ for (int i = 0; i < BatchSize; i++)
560
+ {
561
+ sink ^= (int)staticMethod.Invoke(null, arguments);
562
+ count++;
563
+ }
564
+
565
+ return count;
566
+ },
567
+ () =>
568
+ {
569
+ int count = 0;
570
+ object[] arguments = { 3, 5 };
571
+ for (int i = 0; i < BatchSize; i++)
572
+ {
573
+ sink ^= (int)staticMethodInvoker(arguments);
574
+ count++;
575
+ }
576
+
577
+ return count;
578
+ }
579
+ );
580
+
581
+ yield return Scenario.Create(
582
+ "Constructor Invoke (boxed)",
583
+ () =>
584
+ {
585
+ int count = 0;
586
+ object[] arguments = { 9 };
587
+ for (int i = 0; i < BatchSize; i++)
588
+ {
589
+ ReflectionPerfTarget created = (ReflectionPerfTarget)
590
+ constructor.Invoke(arguments);
591
+ sink ^= created.InstanceField;
592
+ count++;
593
+ }
594
+
595
+ return count;
596
+ },
597
+ () =>
598
+ {
599
+ int count = 0;
600
+ object[] arguments = { 9 };
601
+ for (int i = 0; i < BatchSize; i++)
602
+ {
603
+ ReflectionPerfTarget created = (ReflectionPerfTarget)constructorInvoker(
604
+ arguments
605
+ );
606
+ sink ^= created.InstanceField;
607
+ count++;
608
+ }
609
+
610
+ return count;
611
+ }
612
+ );
613
+ }
614
+
615
+ private sealed class StrategyConfig
616
+ {
617
+ internal StrategyConfig(
618
+ string label,
619
+ bool? expressionsOverride,
620
+ bool? dynamicIlOverride
621
+ )
622
+ {
623
+ Label = label;
624
+ ExpressionsOverride = expressionsOverride;
625
+ DynamicIlOverride = dynamicIlOverride;
626
+ }
627
+
628
+ internal string Label { get; }
629
+
630
+ internal bool? ExpressionsOverride { get; }
631
+
632
+ internal bool? DynamicIlOverride { get; }
633
+
634
+ internal bool RequiresOverride =>
635
+ ExpressionsOverride.HasValue || DynamicIlOverride.HasValue;
636
+ }
637
+
638
+ private sealed class StrategyRunResult
639
+ {
640
+ private StrategyRunResult(
641
+ string label,
642
+ bool supported,
643
+ List<ScenarioResult> boxed,
644
+ List<ScenarioResult> typed,
645
+ string skipReason
646
+ )
647
+ {
648
+ Label = label;
649
+ Supported = supported;
650
+ BoxedResults = boxed ?? new List<ScenarioResult>();
651
+ TypedResults = typed ?? new List<ScenarioResult>();
652
+ SkipReason = skipReason;
653
+ }
654
+
655
+ internal string Label { get; }
656
+
657
+ internal bool Supported { get; }
658
+
659
+ internal IReadOnlyList<ScenarioResult> BoxedResults { get; }
660
+
661
+ internal IReadOnlyList<ScenarioResult> TypedResults { get; }
662
+
663
+ internal string SkipReason { get; }
664
+
665
+ internal static StrategyRunResult Create(
666
+ string label,
667
+ List<ScenarioResult> boxed,
668
+ List<ScenarioResult> typed
669
+ )
670
+ {
671
+ return new StrategyRunResult(label, true, boxed, typed, null);
672
+ }
673
+
674
+ internal static StrategyRunResult CreateUnsupported(string label, string reason)
675
+ {
676
+ return new StrategyRunResult(label, false, null, null, reason);
677
+ }
678
+ }
679
+
680
+ private static IEnumerable<Scenario> CreateTypedScenarios(ReflectionPerfTarget instance)
681
+ {
682
+ Type targetType = typeof(ReflectionPerfTarget);
683
+ FieldInfo instanceField = targetType.GetField(
684
+ nameof(ReflectionPerfTarget.InstanceField)
685
+ );
686
+ FieldInfo staticField = targetType.GetField(nameof(ReflectionPerfTarget.StaticField));
687
+ PropertyInfo instanceProperty = targetType.GetProperty(
688
+ nameof(ReflectionPerfTarget.InstanceProperty)
689
+ );
690
+ PropertyInfo staticProperty = targetType.GetProperty(
691
+ nameof(ReflectionPerfTarget.StaticProperty)
692
+ );
693
+ MethodInfo instanceMethod = targetType.GetMethod(nameof(ReflectionPerfTarget.Combine));
694
+ MethodInfo staticMethod = targetType.GetMethod(
695
+ nameof(ReflectionPerfTarget.StaticCombine)
696
+ );
697
+
698
+ if (
699
+ instanceField == null
700
+ || staticField == null
701
+ || instanceProperty == null
702
+ || staticProperty == null
703
+ || instanceMethod == null
704
+ || staticMethod == null
705
+ )
706
+ {
707
+ throw new InvalidOperationException("ReflectionPerfTarget members not found.");
708
+ }
709
+
710
+ Func<ReflectionPerfTarget, int> instanceFieldGetter = ReflectionHelpers.GetFieldGetter<
711
+ ReflectionPerfTarget,
712
+ int
713
+ >(instanceField);
714
+ FieldSetter<ReflectionPerfTarget, int> instanceFieldSetter =
715
+ ReflectionHelpers.GetFieldSetter<ReflectionPerfTarget, int>(instanceField);
716
+ Func<int> staticFieldGetter = ReflectionHelpers.GetStaticFieldGetter<int>(staticField);
717
+ Action<int> staticFieldSetter = ReflectionHelpers.GetStaticFieldSetter<int>(
718
+ staticField
719
+ );
720
+ Func<ReflectionPerfTarget, int> instancePropertyGetter =
721
+ ReflectionHelpers.GetPropertyGetter<ReflectionPerfTarget, int>(instanceProperty);
722
+ Action<ReflectionPerfTarget, int> instancePropertySetter =
723
+ ReflectionHelpers.GetPropertySetter<ReflectionPerfTarget, int>(instanceProperty);
724
+ Func<int> staticPropertyGetter = ReflectionHelpers.GetStaticPropertyGetter<int>(
725
+ staticProperty
726
+ );
727
+ Action<int> staticPropertySetter = ReflectionHelpers.GetStaticPropertySetter<int>(
728
+ staticProperty
729
+ );
730
+ Func<ReflectionPerfTarget, int, int, int> instanceMethodInvoker =
731
+ ReflectionHelpers.GetInstanceMethodInvoker<ReflectionPerfTarget, int, int, int>(
732
+ instanceMethod
733
+ );
734
+ Func<int, int, int> staticMethodInvoker = ReflectionHelpers.GetStaticMethodInvoker<
735
+ int,
736
+ int,
737
+ int
738
+ >(staticMethod);
739
+
740
+ yield return Scenario.Create(
741
+ "Instance Field Get (typed)",
742
+ () =>
743
+ {
744
+ int count = 0;
745
+ for (int i = 0; i < BatchSize; i++)
746
+ {
747
+ sink ^= instance.InstanceField;
748
+ count++;
749
+ }
750
+
751
+ return count;
752
+ },
753
+ () =>
754
+ {
755
+ int count = 0;
756
+ for (int i = 0; i < BatchSize; i++)
757
+ {
758
+ sink ^= instanceFieldGetter(instance);
759
+ count++;
760
+ }
761
+
762
+ return count;
763
+ }
764
+ );
765
+
766
+ yield return Scenario.Create(
767
+ "Instance Field Set (typed)",
768
+ () =>
769
+ {
770
+ int count = 0;
771
+ int value = 0;
772
+ for (int i = 0; i < BatchSize; i++)
773
+ {
774
+ instance.InstanceField = value;
775
+ sink ^= instance.InstanceField;
776
+ value++;
777
+ count++;
778
+ }
779
+
780
+ return count;
781
+ },
782
+ () =>
783
+ {
784
+ int count = 0;
785
+ int value = 0;
786
+ ReflectionPerfTarget target = instance;
787
+ for (int i = 0; i < BatchSize; i++)
788
+ {
789
+ instanceFieldSetter(ref target, value);
790
+ sink ^= target.InstanceField;
791
+ value++;
792
+ count++;
793
+ }
794
+
795
+ return count;
796
+ }
797
+ );
798
+
799
+ yield return Scenario.Create(
800
+ "Static Field Get (typed)",
801
+ () =>
802
+ {
803
+ int count = 0;
804
+ for (int i = 0; i < BatchSize; i++)
805
+ {
806
+ sink ^= ReflectionPerfTarget.StaticField;
807
+ count++;
808
+ }
809
+
810
+ return count;
811
+ },
812
+ () =>
813
+ {
814
+ int count = 0;
815
+ for (int i = 0; i < BatchSize; i++)
816
+ {
817
+ sink ^= staticFieldGetter();
818
+ count++;
819
+ }
820
+
821
+ return count;
822
+ }
823
+ );
824
+
825
+ yield return Scenario.Create(
826
+ "Static Field Set (typed)",
827
+ () =>
828
+ {
829
+ int count = 0;
830
+ int value = 0;
831
+ for (int i = 0; i < BatchSize; i++)
832
+ {
833
+ ReflectionPerfTarget.StaticField = value;
834
+ sink ^= ReflectionPerfTarget.StaticField;
835
+ value++;
836
+ count++;
837
+ }
838
+
839
+ return count;
840
+ },
841
+ () =>
842
+ {
843
+ int count = 0;
844
+ int value = 0;
845
+ for (int i = 0; i < BatchSize; i++)
846
+ {
847
+ staticFieldSetter(value);
848
+ sink ^= ReflectionPerfTarget.StaticField;
849
+ value++;
850
+ count++;
851
+ }
852
+
853
+ return count;
854
+ }
855
+ );
856
+
857
+ yield return Scenario.Create(
858
+ "Instance Property Get (typed)",
859
+ () =>
860
+ {
861
+ int count = 0;
862
+ for (int i = 0; i < BatchSize; i++)
863
+ {
864
+ sink ^= instance.InstanceProperty;
865
+ count++;
866
+ }
867
+
868
+ return count;
869
+ },
870
+ () =>
871
+ {
872
+ int count = 0;
873
+ for (int i = 0; i < BatchSize; i++)
874
+ {
875
+ sink ^= instancePropertyGetter(instance);
876
+ count++;
877
+ }
878
+
879
+ return count;
880
+ }
881
+ );
882
+
883
+ yield return Scenario.Create(
884
+ "Instance Property Set (typed)",
885
+ () =>
886
+ {
887
+ int count = 0;
888
+ int value = 0;
889
+ for (int i = 0; i < BatchSize; i++)
890
+ {
891
+ instance.InstanceProperty = value;
892
+ sink ^= instance.InstanceProperty;
893
+ value++;
894
+ count++;
895
+ }
896
+
897
+ return count;
898
+ },
899
+ () =>
900
+ {
901
+ int count = 0;
902
+ int value = 0;
903
+ for (int i = 0; i < BatchSize; i++)
904
+ {
905
+ instancePropertySetter(instance, value);
906
+ sink ^= instance.InstanceProperty;
907
+ value++;
908
+ count++;
909
+ }
910
+
911
+ return count;
912
+ }
913
+ );
914
+
915
+ yield return Scenario.Create(
916
+ "Static Property Get (typed)",
917
+ () =>
918
+ {
919
+ int count = 0;
920
+ for (int i = 0; i < BatchSize; i++)
921
+ {
922
+ sink ^= ReflectionPerfTarget.StaticProperty;
923
+ count++;
924
+ }
925
+
926
+ return count;
927
+ },
928
+ () =>
929
+ {
930
+ int count = 0;
931
+ for (int i = 0; i < BatchSize; i++)
932
+ {
933
+ sink ^= staticPropertyGetter();
934
+ count++;
935
+ }
936
+
937
+ return count;
938
+ }
939
+ );
940
+
941
+ yield return Scenario.Create(
942
+ "Static Property Set (typed)",
943
+ () =>
944
+ {
945
+ int count = 0;
946
+ int value = 0;
947
+ for (int i = 0; i < BatchSize; i++)
948
+ {
949
+ ReflectionPerfTarget.StaticProperty = value;
950
+ sink ^= ReflectionPerfTarget.StaticProperty;
951
+ value++;
952
+ count++;
953
+ }
954
+
955
+ return count;
956
+ },
957
+ () =>
958
+ {
959
+ int count = 0;
960
+ int value = 0;
961
+ for (int i = 0; i < BatchSize; i++)
962
+ {
963
+ staticPropertySetter(value);
964
+ sink ^= ReflectionPerfTarget.StaticProperty;
965
+ value++;
966
+ count++;
967
+ }
968
+
969
+ return count;
970
+ }
971
+ );
972
+
973
+ yield return Scenario.Create(
974
+ "Instance Method Invoke (typed)",
975
+ () =>
976
+ {
977
+ int count = 0;
978
+ for (int i = 0; i < BatchSize; i++)
979
+ {
980
+ sink ^= instance.Combine(3, 5);
981
+ count++;
982
+ }
983
+
984
+ return count;
985
+ },
986
+ () =>
987
+ {
988
+ int count = 0;
989
+ for (int i = 0; i < BatchSize; i++)
990
+ {
991
+ sink ^= instanceMethodInvoker(instance, 3, 5);
992
+ count++;
993
+ }
994
+
995
+ return count;
996
+ }
997
+ );
998
+
999
+ yield return Scenario.Create(
1000
+ "Static Method Invoke (typed)",
1001
+ () =>
1002
+ {
1003
+ int count = 0;
1004
+ for (int i = 0; i < BatchSize; i++)
1005
+ {
1006
+ sink ^= ReflectionPerfTarget.StaticCombine(3, 5);
1007
+ count++;
1008
+ }
1009
+
1010
+ return count;
1011
+ },
1012
+ () =>
1013
+ {
1014
+ int count = 0;
1015
+ for (int i = 0; i < BatchSize; i++)
1016
+ {
1017
+ sink ^= staticMethodInvoker(3, 5);
1018
+ count++;
1019
+ }
1020
+
1021
+ return count;
1022
+ }
1023
+ );
1024
+ }
1025
+
1026
+ private static ScenarioResult RunScenario(Scenario scenario)
1027
+ {
1028
+ scenario.Warmup();
1029
+
1030
+ double helperOpsPerSecond = MeasureOpsPerSecond(scenario.Helper);
1031
+ double baselineOpsPerSecond = MeasureOpsPerSecond(scenario.Baseline);
1032
+
1033
+ double speedup =
1034
+ baselineOpsPerSecond <= 0.0
1035
+ ? double.PositiveInfinity
1036
+ : helperOpsPerSecond / baselineOpsPerSecond;
1037
+
1038
+ return new ScenarioResult(
1039
+ scenario.Name,
1040
+ helperOpsPerSecond,
1041
+ baselineOpsPerSecond,
1042
+ speedup
1043
+ );
1044
+ }
1045
+
1046
+ private static double MeasureOpsPerSecond(Func<int> executeBatch)
1047
+ {
1048
+ if (executeBatch == null)
1049
+ {
1050
+ return 0.0;
1051
+ }
1052
+
1053
+ Stopwatch stopwatch = Stopwatch.StartNew();
1054
+ long operations = 0;
1055
+
1056
+ do
1057
+ {
1058
+ operations += executeBatch();
1059
+ } while (stopwatch.Elapsed < BenchmarkDuration);
1060
+
1061
+ double elapsedSeconds = stopwatch.Elapsed.TotalSeconds;
1062
+ if (elapsedSeconds <= 0.0)
1063
+ {
1064
+ elapsedSeconds = 1.0 / Stopwatch.Frequency;
1065
+ }
1066
+
1067
+ return operations / elapsedSeconds;
1068
+ }
1069
+
1070
+ private static string FormatOps(double value)
1071
+ {
1072
+ const double Million = 1_000_000.0;
1073
+ const double Thousand = 1_000.0;
1074
+
1075
+ if (value >= Million)
1076
+ {
1077
+ return string.Format(
1078
+ System.Globalization.CultureInfo.InvariantCulture,
1079
+ "{0:F2}M",
1080
+ value / Million
1081
+ );
1082
+ }
1083
+
1084
+ if (value >= Thousand)
1085
+ {
1086
+ return string.Format(
1087
+ System.Globalization.CultureInfo.InvariantCulture,
1088
+ "{0:F1}K",
1089
+ value / Thousand
1090
+ );
1091
+ }
1092
+
1093
+ return string.Format(
1094
+ System.Globalization.CultureInfo.InvariantCulture,
1095
+ "{0:F0}",
1096
+ value
1097
+ );
1098
+ }
1099
+
1100
+ private static string GetOsToken()
1101
+ {
1102
+ if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
1103
+ {
1104
+ return "WINDOWS";
1105
+ }
1106
+
1107
+ if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
1108
+ {
1109
+ return "MACOS";
1110
+ }
1111
+
1112
+ if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
1113
+ {
1114
+ return "LINUX";
1115
+ }
1116
+
1117
+ return "UNKNOWN";
1118
+ }
1119
+
1120
+ private static string GetScenarioKey(string scenarioName)
1121
+ {
1122
+ if (string.IsNullOrEmpty(scenarioName))
1123
+ {
1124
+ return string.Empty;
1125
+ }
1126
+
1127
+ const string BoxedSuffix = " (boxed)";
1128
+ const string TypedSuffix = " (typed)";
1129
+
1130
+ if (scenarioName.EndsWith(BoxedSuffix, StringComparison.Ordinal))
1131
+ {
1132
+ return scenarioName.Substring(0, scenarioName.Length - BoxedSuffix.Length);
1133
+ }
1134
+
1135
+ if (scenarioName.EndsWith(TypedSuffix, StringComparison.Ordinal))
1136
+ {
1137
+ return scenarioName.Substring(0, scenarioName.Length - TypedSuffix.Length);
1138
+ }
1139
+
1140
+ return scenarioName;
1141
+ }
1142
+
1143
+ private sealed class Scenario
1144
+ {
1145
+ private Scenario(string name, Func<int> baseline, Func<int> helper)
1146
+ {
1147
+ Name = name;
1148
+ Baseline = baseline;
1149
+ Helper = helper;
1150
+ }
1151
+
1152
+ public string Name { get; }
1153
+
1154
+ public Func<int> Baseline { get; }
1155
+
1156
+ public Func<int> Helper { get; }
1157
+
1158
+ public static Scenario Create(string name, Func<int> baseline, Func<int> helper)
1159
+ {
1160
+ if (string.IsNullOrEmpty(name))
1161
+ {
1162
+ throw new ArgumentException("Scenario name must be provided.", nameof(name));
1163
+ }
1164
+
1165
+ if (baseline == null)
1166
+ {
1167
+ throw new ArgumentNullException(nameof(baseline));
1168
+ }
1169
+
1170
+ if (helper == null)
1171
+ {
1172
+ throw new ArgumentNullException(nameof(helper));
1173
+ }
1174
+
1175
+ return new Scenario(name, baseline, helper);
1176
+ }
1177
+
1178
+ public void Warmup()
1179
+ {
1180
+ _ = Baseline();
1181
+ _ = Helper();
1182
+ }
1183
+ }
1184
+
1185
+ private readonly struct ScenarioResult
1186
+ {
1187
+ public ScenarioResult(
1188
+ string name,
1189
+ double helperOpsPerSecond,
1190
+ double baselineOpsPerSecond,
1191
+ double speedup
1192
+ )
1193
+ {
1194
+ Name = name;
1195
+ HelperOpsPerSecond = helperOpsPerSecond;
1196
+ BaselineOpsPerSecond = baselineOpsPerSecond;
1197
+ Speedup = speedup;
1198
+ }
1199
+
1200
+ public string Name { get; }
1201
+
1202
+ public double HelperOpsPerSecond { get; }
1203
+
1204
+ public double BaselineOpsPerSecond { get; }
1205
+
1206
+ public double Speedup { get; }
1207
+ }
1208
+
1209
+ private sealed class ReflectionPerfTarget
1210
+ {
1211
+ public static int StaticField;
1212
+
1213
+ public static int StaticProperty { get; set; }
1214
+
1215
+ public int InstanceField;
1216
+
1217
+ public int InstanceProperty { get; set; }
1218
+
1219
+ public ReflectionPerfTarget() { }
1220
+
1221
+ public ReflectionPerfTarget(int value)
1222
+ {
1223
+ InstanceField = value;
1224
+ InstanceProperty = value;
1225
+ }
1226
+
1227
+ public int Combine(int first, int second)
1228
+ {
1229
+ return InstanceField + first + second;
1230
+ }
1231
+
1232
+ public static int StaticCombine(int first, int second)
1233
+ {
1234
+ return StaticField + first + second;
1235
+ }
1236
+ }
1237
+ }
1238
+ }