Иногда рефлексивные вызовы дороги в терминах производительности и не могут быть опущены.
В этой статье представлены паттерны, позволяющие существенно повысить производительность множественных рефлексивных вызовов посредством техники виртуализации (кэширования) результатов.
Рассмотрим следующий метод:
static void AnyPerformanceCriticalMethod()
{
var anyTypeName = typeof(AnyType).Name;
/* ... using of anyTypeName ... */
}
Важный паттерн в подобных случаях — это кэширование локальной переменной в качестве статической, например:
/* much better! */
static readonly string AnyTypeName = typeof(AnyType).Name;
static void AnyPerformanceCriticalMethod()
{
/* ... using of AnyTypeName ... */
}
Но как поступить в случае обобщённого (generic) метода?
static void AnyPerformanceCriticalMethod<T>()
{
var anyTypeName = typeof(T).Name;
/* ... using of anyTypeName ... */
}
Существует практичное обобщённое решение, взгляните.
public static class TypeOf<T>
{
/* Important! Should be a readonly variable for best performance */
public static readonly Type Raw = typeof(T);
public static readonly string Name = Raw.Name;
public static readonly Assembly Assembly = Raw.Assembly;
public static readonly bool IsValueType = Raw.IsValueType;
/* etc. */
}
static void AnyPerformanceCriticalMethod<T>()
{
var anyTypeName = TypeOf<T>.Name;
/* ... using of anyTypeName ... */
}
*Примечательно, что до момента добавления обобщённых классов и методов, C# уже имел поддержку ряда обобщённых операторов: `typeof`, `is`, `as`
Что насчёт другого сценария?
static void AnyPerformanceCriticalMethod(object item)
{
var itemTypeName = o.GetType().Name;
/* ... using of anyTypeName ... */
}
Можем попробовать.
public class RipeType
{
internal RipeType(Type raw)
{
Raw = raw;
Name = raw.Name;
Assembly = raw.Assembly;
IsValueType = raw.IsValueType;
/* etc. */
}
public static Type Raw { get; }
public string Name { get; }
public Assembly Assembly { get; }
public bool IsValueType { get; }
/* etc. */
}
public static class TypeOf
{
public static readonly Dictionary<Type, RipeType> RawToRipe = new Dictionary<Type, RipeType>();
public static RipeType ToRipeType(this Type type) =>
RawToRipe.TryGetValue(type, out var typeData)
? typeData
: Lock.Invoke(() => RawToRipe.TryGetValue(type, out typeData)
? typeData // may catch item created into a different thread
: RawToRipe[type] = new RipeType(type));
public static RipeType GetRipeType(this object o) => o.GetType().ToRipeType();
}
public static class Lock
{
public static TResult Invoke<TResult>(Func<TResult> func)
{
lock (func) return func();
}
}
Итак, теперь можно использовать:
static void AnyPerformanceCriticalMethod(object item)
{
var itemTypeName = o.GetRipeType().Name;
/* ... using of anyTypeName ... */
}
Что насчёт недостатков `TypeOf` паттерна?
* `typeof(List<>)` допустимо
* `TypeOf<List<>>` не допустимо
Как решить?
var listAssemby = TypeOf.List.Assembly;
где
public static class TypeOf
{
/* ... */
public static readonly RipeType Object = typeof(object).ToRipeType();
public static readonly RipeType String = typeof(string).ToRipeType();
public static readonly RipeType Array = typeof(Array).ToRipeType();
public static readonly RipeType Type = typeof(Type).ToRipeType();
public static readonly RipeType List = typeof(List<>).ToRipeType();
public static readonly RipeType IList = typeof(IList<>).ToRipeType();
public static readonly RipeType Dictionary = typeof(Dictionary<,>).ToRipeType();
public static readonly RipeType IDictionary = typeof(IDictionary<,>).ToRipeType();
public static readonly RipeType KeyValuePair = typeof(KeyValuePair<,>).ToRipeType();
public static readonly RipeType DictionaryEntry = typeof(DictionaryEntry).ToRipeType();
}
Самое время для бенчмарков!
[
CoreJob,
ClrJob,
MonoJob("Mono", @"C:Program FilesMonobinmono.exe")
]
public class TypeOfBenchmarks
{
[Benchmark] public Type typeof_int() => typeof(int);
[Benchmark] public Type TypeOf_int() => TypeOf<int>.Raw;
[Benchmark] public Type typeof_string() => typeof(string);
[Benchmark] public Type TypeOf_string() => TypeOf<string>.Raw;
[Benchmark] public string typeof_int_Name() => typeof(int).Name;
[Benchmark] public string TypeOf_int_Name() => TypeOf<int>.Name;
[Benchmark] public string typeof_string_Name() => typeof(string).Name;
[Benchmark] public string TypeOf_string_Name() => TypeOf<string>.Name;
[Benchmark] public Assembly typeof_int_Assembly() => typeof(int).Assembly;
[Benchmark] public Assembly TypeOf_int_Assembly() => TypeOf<int>.Assembly;
[Benchmark] public Assembly typeof_string_Assembly() => typeof(string).Assembly;
[Benchmark] public Assembly TypeOf_string_Assembly() => TypeOf<string>.Assembly;
[Benchmark] public bool typeof_int_IsValueType() => typeof(int).IsValueType;
[Benchmark] public bool TypeOf_int_IsValueType() => TypeOf<int>.IsValueType;
[Benchmark] public bool typeof_string_IsValueType() => typeof(string).IsValueType;
[Benchmark] public bool TypeOf_string_IsValueType() => TypeOf<string>.IsValueType;
}
Total time: 00:23:34 (1414.47 sec)
// * Summary *
BenchmarkDotNet=v0.10.14, OS=Windows 10.0.17134
Intel Core i7-3517U CPU 1.90GHz (Ivy Bridge), 1 CPU, 4 logical and 2 physical cores
Frequency=2338440 Hz, Resolution=427.6355 ns, Timer=TSC
.NET Core SDK=2.1.302
[Host] : .NET Core 2.1.2 (CoreCLR 4.6.26628.05, CoreFX 4.6.26629.01), 64bit RyuJIT
Clr : .NET Framework 4.7.1 (CLR 4.0.30319.42000), 64bit RyuJIT-v4.7.3160.0
Core : .NET Core 2.1.2 (CoreCLR 4.6.26628.05, CoreFX 4.6.26629.01), 64bit RyuJIT
Mono : Mono 5.12.0 (Visual Studio), 64bit
Method | Job | Runtime | Mean | Error | StdDev |
-------------------------- |----- |-------- |------------:|-----------:|-----------:|
typeof_int | Clr | Clr | 3.2686 ns | 0.0490 ns | 0.0434 ns |
TypeOf_int | Clr | Clr | 0.0495 ns | 0.1124 ns | 0.0939 ns |
typeof_string | Clr | Clr | 3.1980 ns | 0.0288 ns | 0.0270 ns |
TypeOf_string | Clr | Clr | 0.0520 ns | 0.0773 ns | 0.0723 ns |
typeof_int_Name | Clr | Clr | 19.4201 ns | 0.1220 ns | 0.1141 ns |
TypeOf_int_Name | Clr | Clr | 0.0082 ns | 0.0169 ns | 0.0159 ns |
typeof_string_Name | Clr | Clr | 19.5041 ns | 0.1397 ns | 0.1090 ns |
TypeOf_string_Name | Clr | Clr | 0.0007 ns | 0.0031 ns | 0.0028 ns |
typeof_int_Assembly | Clr | Clr | 33.8565 ns | 0.6931 ns | 0.5788 ns |
TypeOf_int_Assembly | Clr | Clr | 0.0034 ns | 0.0130 ns | 0.0115 ns |
typeof_string_Assembly | Clr | Clr | 33.9922 ns | 0.2244 ns | 0.1989 ns |
TypeOf_string_Assembly | Clr | Clr | 0.0001 ns | 0.0004 ns | 0.0003 ns |
typeof_int_IsValueType | Clr | Clr | 56.1685 ns | 0.3858 ns | 0.3420 ns |
TypeOf_int_IsValueType | Clr | Clr | 0.4990 ns | 0.0141 ns | 0.0132 ns |
typeof_string_IsValueType | Clr | Clr | 94.0358 ns | 0.4386 ns | 0.3662 ns |
TypeOf_string_IsValueType | Clr | Clr | 0.4960 ns | 0.0109 ns | 0.0102 ns |
typeof_int | Core | Core | 1.9114 ns | 0.0527 ns | 0.0493 ns |
TypeOf_int | Core | Core | 6.1310 ns | 0.0494 ns | 0.0462 ns |
typeof_string | Core | Core | 2.2120 ns | 0.0522 ns | 0.0436 ns |
TypeOf_string | Core | Core | 6.1174 ns | 0.0481 ns | 0.0401 ns |
typeof_int_Name | Core | Core | 19.5100 ns | 0.1998 ns | 0.1771 ns |
TypeOf_int_Name | Core | Core | 6.1495 ns | 0.0829 ns | 0.0735 ns |
typeof_string_Name | Core | Core | 19.3662 ns | 0.0895 ns | 0.0793 ns |
TypeOf_string_Name | Core | Core | 6.1589 ns | 0.0314 ns | 0.0278 ns |
typeof_int_Assembly | Core | Core | 23.4876 ns | 0.1885 ns | 0.1763 ns |
TypeOf_int_Assembly | Core | Core | 6.1362 ns | 0.0415 ns | 0.0388 ns |
typeof_string_Assembly | Core | Core | 25.5613 ns | 0.2293 ns | 0.2033 ns |
TypeOf_string_Assembly | Core | Core | 6.1082 ns | 0.0352 ns | 0.0312 ns |
typeof_int_IsValueType | Core | Core | 49.8048 ns | 0.2305 ns | 0.1925 ns |
TypeOf_int_IsValueType | Core | Core | 7.1171 ns | 0.0477 ns | 0.0423 ns |
typeof_string_IsValueType | Core | Core | 84.8155 ns | 0.7962 ns | 0.7058 ns |
TypeOf_string_IsValueType | Core | Core | 7.0987 ns | 0.0521 ns | 0.0487 ns |
typeof_int | Mono | Mono | 0.0725 ns | 0.0229 ns | 0.0214 ns |
TypeOf_int | Mono | Mono | 3.0123 ns | 0.0652 ns | 0.0610 ns |
typeof_string | Mono | Mono | 0.0185 ns | 0.0206 ns | 0.0193 ns |
TypeOf_string | Mono | Mono | 9.3828 ns | 0.0863 ns | 0.0765 ns |
typeof_int_Name | Mono | Mono | 429.8195 ns | 4.4049 ns | 3.6783 ns |
TypeOf_int_Name | Mono | Mono | 2.3856 ns | 0.1608 ns | 0.1426 ns |
typeof_string_Name | Mono | Mono | 439.3774 ns | 1.2985 ns | 1.2146 ns |
TypeOf_string_Name | Mono | Mono | 8.8580 ns | 0.0728 ns | 0.0646 ns |
typeof_int_Assembly | Mono | Mono | 223.5933 ns | 0.6152 ns | 0.5454 ns |
TypeOf_int_Assembly | Mono | Mono | 2.2587 ns | 0.0494 ns | 0.0462 ns |
typeof_string_Assembly | Mono | Mono | 227.3259 ns | 0.6448 ns | 0.5716 ns |
TypeOf_string_Assembly | Mono | Mono | 9.3276 ns | 0.1215 ns | 0.1136 ns |
typeof_int_IsValueType | Mono | Mono | 490.2376 ns | 4.3860 ns | 4.1027 ns |
TypeOf_int_IsValueType | Mono | Mono | 3.1849 ns | 0.0145 ns | 0.0129 ns |
typeof_string_IsValueType | Mono | Mono | 997.4254 ns | 11.6159 ns | 10.8655 ns |
TypeOf_string_IsValueType | Mono | Mono | 9.6504 ns | 0.0354 ns | 0.0331 ns |
[
CoreJob,
ClrJob,
MonoJob("Mono", @"C:Program FilesMonobinmono.exe")
]
public class RipeTypeBenchmarks
{
static object o = new object();
readonly Type rawType = o.GetType();
readonly RipeType ripeType = o.GetRipeType();
[Benchmark] public string RawType_Name() => rawType.Name;
[Benchmark] public string RipeType_Name() => ripeType.Name;
[Benchmark] public string GetRawType_Name() => o.GetType().Name;
[Benchmark] public string GetRipeType_Name() => o.GetRipeType().Name;
[Benchmark] public Assembly RawType_Assembly() => rawType.Assembly;
[Benchmark] public Assembly RipeType_Assembly() => ripeType.Assembly;
[Benchmark] public Assembly GetRawType_Assembly() => o.GetType().Assembly;
[Benchmark] public Assembly GetRipeType_Assembly() => o.GetRipeType().Assembly;
[Benchmark] public bool RawType_IsValueType() => rawType.IsValueType;
[Benchmark] public bool RipeType_IsValueType() => ripeType.IsValueType;
[Benchmark] public bool GetRawType_IsValueType() => o.GetType().IsValueType;
[Benchmark] public bool GetRipeType_IsValueType() => o.GetRipeType().IsValueType;
}
Total time: 00:14:59 (899.57 sec)
// * Summary *
BenchmarkDotNet=v0.10.14, OS=Windows 10.0.17134
Intel Core i7-3517U CPU 1.90GHz (Ivy Bridge), 1 CPU, 4 logical and 2 physical cores
Frequency=2338440 Hz, Resolution=427.6355 ns, Timer=TSC
.NET Core SDK=2.1.302
[Host] : .NET Core 2.1.2 (CoreCLR 4.6.26628.05, CoreFX 4.6.26629.01), 64bit RyuJIT
Clr : .NET Framework 4.7.1 (CLR 4.0.30319.42000), 64bit RyuJIT-v4.7.3160.0
Core : .NET Core 2.1.2 (CoreCLR 4.6.26628.05, CoreFX 4.6.26629.01), 64bit RyuJIT
Mono : Mono 5.12.0 (Visual Studio), 64bit
Method | Job | Runtime | Mean | Error | StdDev |
------------------------ |----- |-------- |------------:|----------:|----------:|
RawType_Name | Clr | Clr | 10.2733 ns | 0.1112 ns | 0.1040 ns |
RipeType_Name | Clr | Clr | 0.0164 ns | 0.0220 ns | 0.0206 ns |
GetRawType_Name | Clr | Clr | 15.3661 ns | 0.4064 ns | 0.7431 ns |
GetRipeType_Name | Clr | Clr | 43.3530 ns | 0.4160 ns | 0.3474 ns |
RawType_Assembly | Clr | Clr | 19.8898 ns | 0.1967 ns | 0.1840 ns |
RipeType_Assembly | Clr | Clr | 0.0002 ns | 0.0010 ns | 0.0009 ns |
GetRawType_Assembly | Clr | Clr | 22.7084 ns | 0.1512 ns | 0.1340 ns |
GetRipeType_Assembly | Clr | Clr | 43.1685 ns | 0.3532 ns | 0.3304 ns |
RawType_IsValueType | Clr | Clr | 35.7668 ns | 0.2840 ns | 0.2517 ns |
RipeType_IsValueType | Clr | Clr | 0.0005 ns | 0.0020 ns | 0.0018 ns |
GetRawType_IsValueType | Clr | Clr | 39.6176 ns | 0.2465 ns | 0.2306 ns |
GetRipeType_IsValueType | Clr | Clr | 43.4645 ns | 0.9240 ns | 0.8643 ns |
RawType_Name | Core | Core | 10.7102 ns | 0.1705 ns | 0.1511 ns |
RipeType_Name | Core | Core | 0.0075 ns | 0.0154 ns | 0.0144 ns |
GetRawType_Name | Core | Core | 12.8294 ns | 0.0698 ns | 0.0653 ns |
GetRipeType_Name | Core | Core | 38.7723 ns | 0.2665 ns | 0.2493 ns |
RawType_Assembly | Core | Core | 13.1644 ns | 0.0729 ns | 0.0682 ns |
RipeType_Assembly | Core | Core | 0.0174 ns | 0.0207 ns | 0.0194 ns |
GetRawType_Assembly | Core | Core | 15.3733 ns | 0.1252 ns | 0.1110 ns |
GetRipeType_Assembly | Core | Core | 38.7863 ns | 0.3133 ns | 0.2616 ns |
RawType_IsValueType | Core | Core | 32.9788 ns | 0.4456 ns | 0.3721 ns |
RipeType_IsValueType | Core | Core | 0.0365 ns | 0.0128 ns | 0.0107 ns |
GetRawType_IsValueType | Core | Core | 35.4362 ns | 0.2927 ns | 0.2595 ns |
GetRipeType_IsValueType | Core | Core | 39.8377 ns | 0.2895 ns | 0.2708 ns |
RawType_Name | Mono | Mono | 287.4362 ns | 2.3812 ns | 2.2274 ns |
RipeType_Name | Mono | Mono | 0.4614 ns | 0.0320 ns | 0.0299 ns |
GetRawType_Name | Mono | Mono | 288.2094 ns | 2.2540 ns | 2.1084 ns |
GetRipeType_Name | Mono | Mono | 54.3390 ns | 0.2807 ns | 0.2625 ns |
RawType_Assembly | Mono | Mono | 143.6474 ns | 0.7524 ns | 0.7038 ns |
RipeType_Assembly | Mono | Mono | 0.7015 ns | 0.0261 ns | 0.0244 ns |
GetRawType_Assembly | Mono | Mono | 144.0314 ns | 3.2279 ns | 3.0194 ns |
GetRipeType_Assembly | Mono | Mono | 54.5511 ns | 0.2955 ns | 0.2619 ns |
RawType_IsValueType | Mono | Mono | 277.4973 ns | 1.4938 ns | 1.3242 ns |
RipeType_IsValueType | Mono | Mono | 0.5206 ns | 0.0176 ns | 0.0156 ns |
GetRawType_IsValueType | Mono | Mono | 280.7464 ns | 2.1995 ns | 1.8367 ns |
GetRipeType_IsValueType | Mono | Mono | 58.5908 ns | 0.1690 ns | 0.1498 ns |
using System;
using System.Diagnostics;
using System.Linq;
using Ace.Base.Benchmarking.Benchmarks;
using BenchmarkDotNet.Running;
namespace Ace.Base.Benchmarking
{
static class Program
{
private const long WarmRunsCount = 1000;
private const long HotRunsCount = 10000000; // 10 000 000
static void Main()
{
//BenchmarkRunner.Run<TypeOfBenchmarks>();
//BenchmarkRunner.Run<RipeTypeBenchmarks>();
TypeofVsTypeOf();
RawTypeVsRipeType();
Console.ReadKey();
}
static void RawTypeVsRipeType()
{
Console.WriteLine();
Console.WriteLine($"Count of warm iterations: {WarmRunsCount}");
Console.WriteLine($"Count of hot iterations: {HotRunsCount}");
Console.WriteLine();
var o = new object();
var rawType = o.GetType();
var ripeType = o.GetRipeType();
RunBenchmarks(
(() => rawType.Name, "() => rawType.Name"),
(() => ripeType.Name, "() => ripeType.Name"),
(() => o.GetType().Name, "() => o.GetType().Name"),
(() => o.GetRipeType().Name, "() => o.GetRipeType().Name")
);
Console.WriteLine();
RunBenchmarks(
(() => rawType.Assembly, "() => rawType.Assembly"),
(() => ripeType.Assembly, "() => ripeType.Assembly"),
(() => o.GetType().Assembly, "() => o.GetType().Assembly"),
(() => o.GetRipeType().Assembly, "() => o.GetRipeType().Assembly")
);
Console.WriteLine();
RunBenchmarks(
(() => rawType.IsValueType, "() => rawType.IsValueType"),
(() => ripeType.IsValueType, "() => ripeType.IsValueType"),
(() => o.GetType().IsValueType, "() => o.GetType().IsValueType"),
(() => o.GetRipeType().IsValueType, "() => o.GetRipeType().IsValueType")
);
}
static void TypeofVsTypeOf()
{
Console.WriteLine($"Count of warm iterations: {WarmRunsCount}");
Console.WriteLine($"Count of hot iterations: {HotRunsCount}");
Console.WriteLine();
RunBenchmarks(
(() => typeof(int), "() => typeof(int)"),
(() => TypeOf<int>.Raw, "() => TypeOf<int>.Raw"),
(() => typeof(string), "() => typeof(string)"),
(() => TypeOf<string>.Raw, "() => TypeOf<string>.Raw")
);
Console.WriteLine();
RunBenchmarks(
(() => typeof(int).Name, "() => typeof(int).Name"),
(() => TypeOf<int>.Name, "() => TypeOf<int>.Name"),
(() => typeof(string).Name, "() => typeof(string).Name"),
(() => TypeOf<string>.Name, "() => TypeOf<string>.Name")
);
Console.WriteLine();
RunBenchmarks(
(() => typeof(int).Assembly, "() => typeof(int).Assembly"),
(() => TypeOf<int>.Assembly, "() => TypeOf<int>.Assembly"),
(() => typeof(string).Assembly, "() => typeof(string).Assembly"),
(() => TypeOf<string>.Assembly, "() => TypeOf<string>.Assembly")
);
Console.WriteLine();
RunBenchmarks(
(() => typeof(int).IsValueType, "() => typeof(int).IsValueType"),
(() => TypeOf<int>.IsValueType, "() => TypeOf<int>.IsValueType"),
(() => typeof(string).IsValueType, "() => typeof(string).IsValueType"),
(() => TypeOf<string>.IsValueType, "() => TypeOf<string>.IsValueType")
);
}
static void RunBenchmarks<T>(params (Func<T> Func, string StringRepresentation)[] funcAndViewTuples) =>
funcAndViewTuples
.Select(t => (
BenchmarkResults: t.Func.InvokeBenchmark(HotRunsCount, WarmRunsCount),
StringRepresentation: t.StringRepresentation))
.ToList().ForEach(t =>
Console.WriteLine(
$"{t.StringRepresentation}t{t.BenchmarkResults.Result}t{t.BenchmarkResults.ElapsedMilliseconds} (ms)"));
static (Func<T> Func, long ElapsedMilliseconds, T Result) InvokeBenchmark<T>(this Func<T> func,
long hotRunsCount, long warmRunsCount)
{
var stopwatch = new Stopwatch();
var result = default(T);
for (var i = 0L; i < warmRunsCount; i++)
result = func();
stopwatch.Start();
for (var i = 0L; i < hotRunsCount; i++)
result = func();
stopwatch.Stop();
return (func, stopwatch.ElapsedMilliseconds, result);
}
}
}
Count of warm iterations: 1000
Count of hot iterations: 10000000
() => typeof(int) System.Int32 70 (ms)
() => TypeOf<int>.Raw System.Int32 106 (ms)
() => typeof(string) System.String 70 (ms)
() => TypeOf<string>.Raw System.String 101 (ms)
() => typeof(int).Name Int32 249 (ms)
() => TypeOf<int>.Name Int32 42 (ms)
() => typeof(string).Name String 245 (ms)
() => TypeOf<string>.Name String 48 (ms)
() => typeof(int).Assembly System.Private.CoreLib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e 285 (ms)
() => TypeOf<int>.Assembly System.Private.CoreLib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e 42 (ms)
() => typeof(string).Assembly System.Private.CoreLib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e 340 (ms)
() => TypeOf<string>.Assembly System.Private.CoreLib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e 47 (ms)
() => typeof(int).IsValueType True 544 (ms)
() => TypeOf<int>.IsValueType True 53 (ms)
() => typeof(string).IsValueType False 889 (ms)
() => TypeOf<string>.IsValueType False 47 (ms)
Count of warm iterations: 1000
Count of hot iterations: 10000000
() => rawType.Name Object 221 (ms)
() => ripeType.Name Object 42 (ms)
() => o.GetType().Name Object 250 (ms)
() => o.GetRipeType().Name Object 687 (ms)
() => rawType.Assembly System.Private.CoreLib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e 271 (ms)
() => ripeType.Assembly System.Private.CoreLib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e 42 (ms)
() => o.GetType().Assembly System.Private.CoreLib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e 330 (ms)
() => o.GetRipeType().Assembly System.Private.CoreLib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e 686 (ms)
() => rawType.IsValueType False 553 (ms)
() => ripeType.IsValueType False 47 (ms)
() => o.GetType().IsValueType False 590 (ms)
() => o.GetRipeType().IsValueType False 711 (ms)
Заключение
`TypeOf` и `RipeType` паттерны позволяют ощутимо улучшить производительность множественных рекурсивных вызовов в некоторых сценариях на различных CLR.
Автор: Makeman