typeof(T) vs. TypeOf⟨T⟩

    Иногда рефлексивные вызовы дороги в терминах производительности и не могут быть опущены.

    В этой статье представлены паттерны, позволяющие существенно повысить производительность множественных рефлексивных вызовов посредством техники виртуализации (кэширования) результатов.

    image

    Рассмотрим следующий метод:

    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 ... */
    }
    

    Существует практичное обобщённое решение, взгляните.

    TypeOf.cs
    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 ... */
    }
    

    Можем попробовать.

    RipeType.cs
    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. */
    }
    

    TypeOf.cs
    * Примечание: как указали в комментариях, для более надёжной потокобезопасности нужно использовать ConcurrentDictionary (это может повлиять на результаты тестов)
    public static class TypeOf
    {
    	private static readonly object SyncRoot = new object();
    
    	private 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(SyncRoot, () => 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();
    }
    

    Lock.cs
    public static class Lock
    {
    	public static void Invoke<TSyncContext>(TSyncContext customSyncContext, Action action)
    	{
    		lock (customSyncContext) action();
    	}
    
    	public static TResult Invoke<TSyncContext, TResult>(TSyncContext customSyncContext, Func<TResult> func)
    	{
    		lock (customSyncContext) 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();
    }
    

    Самое время для бенчмарков!

    typeof vs. TypeOf [BenchmarkDotNet - code]
    [
    	CoreJob,
    	ClrJob,
    	MonoJob("Mono", @"C:\Program Files\Mono\bin\mono.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;
    }


    typeof vs. TypeOf [BenchmarkDotNet - results]
    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 |


    Type vs. RipeType [BenchmarkDotNet - code]
    [
    	CoreJob,
    	ClrJob,
    	MonoJob("Mono", @"C:\Program Files\Mono\bin\mono.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;
    }


    Type vs. RipeType [BenchmarkDotNet - results]
    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 |
    


    Manual benchmarks - Code (much faster runs)
    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);
            }
        }
    }
    


    Manual benchmarks - Results on Core CLR
    
    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.

    Similar posts

    Ads
    AdBlock has stolen the banner, but banners are not teeth — they will be back

    More

    Comments 229

      +5

      Dictionary<Type, RipeType> — непотокобезопасный контейнер. Нельзя его в одном потоке читать, а в другом в то же самое время писать! Да и завязываться на то что компилятор закеширует передаваемый в Lock.Invoke делегат тоже не стоит...


      Почему бы не использовать ConcurrentDictionary вместо велосипеда?

        –4
        Можно и ConcurrentDictionary использовать, если нужно.

        По началу у меня самого были подозрения насчёт такого решения, но при более детальном анализе я пришёл к выводу, что оно довольно безопасное. Буду признателен, если вы всё же укажете на возможный сценарий, приводящий к ошибке… Мне самому интересно о нём узнать, если он существует.
        Да и завязываться на то что компилятор закеширует передаваемый в Lock.Invoke делегат тоже не стоит...

        Если в разных потоках создать два делегата от одного метода, то они будут равны, поэтому lock сработает корректно.
          +3
          Буду признателен, если вы всё же укажете на возможный сценарий, приводящий к ошибке…

          Пока один поток меняет словарь, второй из него читает и получает мусорные данные. Например, не до конца заполненный RipeType. Или падает с NPE из-за видимого нарушения внутренней структуры словаря.

            –1
            Внешняя ссылка на экземпляр класса RipeType может появится только после выполнения конструктора, в каком бы потоке мы ни выполняли оператор new. Исключение составляет лишь случай вроде
                class AnyClass
                {
                    public static AnyClass Instance;
            
                    public AnyClass()
                    {
                        Instance = this;
                        /* ... */
                    }
                }

            Но это не наша ситуация, поэтому вариант с недоинициализированным RipeType отпадает.

            По логике вещей, при чтении структура словаря не может быть нарушена, даже если оно идёт из разных потоков. При параллельной записи тоже, поскольку есть lock. Остаётся лишь случай чтения в момент записи… Мне думается, что словарь не бросит исключение от такого, а если вдруг чтение произошло до момента вставки только что созданного экземпляра и вернулся null, то мы направляемся в lock и дожидаемся завершения вставки, после чего повторяем чтение и получаем уже созданный экземпляр.
              +2
              Вы же понимаете, что чтение/запись в хэш-таблицу — это не атомарные операции?

              З. Ы. Блокировка на делегате — это совсем жесть, конечно.
                –1
                Понимаю, конечно. Но в данном решении атомарность и не требуется за счёт повторного чтения под локом.

                С непривычки да, но как бы должно работать, поскольку компилятор обеспечивает однозначный и потокобезопасный контекст блокировки.
                  +1
                  Но в данном решении атомарность и не требуется за счёт повторного чтения под локом.

                  То есть то, что у вас чтение бывает для разных ключей, вы и забыли, да?

                    0
                    Повторное чтение происходит по тому же ключу, что и при первой неудачной попытке.
                      +1
                      Что произойдет при первом чтении по двум разным ключам «одновременно» из двух потоков?

                      Боттлнек на пустом месте, да.
                        +1

                        … вот именно поэтому не надо придумывать свою реализацию, не разобравшись в проблеме.


                        Вот у вас есть словарь, в нем есть значение для ключа A. Теперь к вам одновременно пришли запросы для ключей A и B. Первый попадет в чтение, второй — в запись, и они могут идти строго одновременно, потому что на первый не распространяется лок.

                      +2

                      Не, не должно. У вас каждый раз при входе в метод будет создаваться новый экземпляр скрытого типа, и поэтому lock всегда будет получать новый объект.

                    +1
                    Мне думается, что словарь не бросит исключение от такого

                    Не, не бросит. Просто тихо вернет не то значение. Вы внутрь TryGetValue никогда не заглядывали?

                      0
                      Заглядывал когда-то давно. Конечно, могу ошибаться, но на вскидку такое маловероятно, поскольку если не найдено соответствия по ключу, то с чего бы возвращать дугое значение. Теоретически, может быть такое, что ключ уже попал в словарь, а значение пока ещё не присвоилось, но тогда хотя бы дефолтный null вернуться должен.

                      Я прямо говорю, что мной выбрано такое решение в целях эксперимента и обсуждения, поскольку есть подозрение, что оно рабочее.
                        +2
                        Там внутри таблица связных списков поверх массива с ресайзом этого самого массива, в ходе которого элементы могут быть заново переразложены по спискам с пересчетом хэшкодов со случайной солью. То есть, никто не гарантирует, что при пересчете элементы хэш-таблицы «случайно» не обменяются хэшкодами.

                        UPD github.com/Microsoft/referencesource/blob/60a4f8b853f60a424e36c7bf60f9b5b5f1973ed1/mscorlib/system/collections/generic/dictionary.cs#L386
                          –2
                          Обменяться-то они могут, но по хэш-коду вычисляется номер «корзины» (связного списка), а поиск в списке уже идёт по строгой эквивалентности ключа, поэтому в худшем случае элемент может не найтись, хотя он в словаре присутствует (и-то мне видится это крайне маловероятным событием, если вообще возможным).
                            +1
                            Только вот корзина — это связный список на индексах. И все корзины лежат в одном массиве, а индексы у элементов меняются, причем перемещение элемента неатомарное и включает, кажется, с полдесятка операций записи и зависит не то чтобы от конкретного рантайма, а от конкретной его сборки.

                            Построить полную картину того, что может произойти при контеншне не берусь, там сотни вариантов с разным исходом, в том числе возврате любого произвольного элемента вместо null.

                            Самый простой вам ниже lair показал.
                              +3

                              Я например видел такое маловероятное событие на проде два раза. Один раз ночью в выходной

                            +2
                            Конечно, могу ошибаться, но на вскидку такое маловероятно, поскольку если не найдено соответствия по ключу, то с чего бы возвращать дугое значение.

                            А вы загляните.


                            int entry = this.FindEntry(key);
                            if (entry >= 0)
                            {
                              value = this.entries[entry].value;
                              return true;
                            }

                            Вот если после FindEntry массив entries поменяет размер (с перекладкой всего), а именно это происходит (иногда) при добавлении новой записи, значение по индексу (entries[entry]) будет совсем не от нужной записи.


                            Я прямо говорю, что мной выбрано такое решение в целях эксперимента и обсуждения, поскольку есть подозрение, что оно рабочее.

                            Неправильное подозрение. Dictionary<K,V> нельзя использовать в сценариях конкурентного чтения/записи без явной блокировки всех операций.

                              +1
                              поменяет размер (с перекладкой всего)

                              … на самом деле, немного сложнее, потому что entries в той реализации, на которую я смотрю, никогда не перемешивается. Зато внутри FindEntry есть прекрасный код, смотрит на this.buckets[num % this.buckets.Length] и в зависимости от настроения оптимизатора в этом месте можно получить что угодно, включая погоду на Марсе, например, когда между обращением к buckets.Length и обращением к buckets[x] содержимое buckets поменялось — а вот buckets точно перемешивается.

                                –1
                                Это, конечно, интересный момент, но по беглому изучению кода выглядит так, что entries при добавлении новых элементов может лишь увеличиваться в размере, а перекладка элементов в новый массив происходит без смешивания, через Array.Copy, поэтому даже старый индекс будет валиден в случае нового массива, вопрос остаётся открытым…
                                    –1
                                    Выглядит так, что в худшем случае мы можем лишь потерять элемент, уже находящийся в словаре, что приведёт к его пересозданию извне, но если элемент по ключу найден, то чтение безопасно, поскольку индеск в массиве за ним закрепляется навсегда.
                                      +1
                                      Выглядит так, что в худшем случае мы можем лишь потерять элемент, уже находящийся в словаре, что приведёт к его пересозданию извне

                                      … а поскольку пересоздание внутри себя тоже не потокобезопасно, там можно получить исключение. И это не единственный возможный сценарий.

                                        –2
                                        Насчёт пересоздания тоже ещё вопрос, но в нашем случае даже при таком неудачном раскладе исключения точно не будет, просто создастся новый экземпляр и по ключу заменит старый в словаре.
                                          +1
                                          исключения точно не будет

                                          Вы проверил все возможные сценарии, со всеми расположениями переменных? Например, что случится, если у вас происходит два одновременных добавления, и два потока одновременно заберут ссылку на следующий свободный элемент, и там окажется хэш-код и ключ от одного элемента, а значение — от другого?

                                            –2
                                            Нет, я не проверял все возможные сценарии и отталкиваюсь лишь от того, что TryGetValue возвращает true и сам элемент, если он присутствует в словаре по ключу, либо false если его там нет или он только что асинхронно добавлен в процессе чтения другим потоком (и на этот случай выполняется повторное чтение в критической секции).
                                              +1

                                              Вы, повторюсь, забыли, что у вас параллельно еще присвоения идут?

                                                –2
                                                Запись элементов идёт только под lock'ом. То есть я допускаю лишь ситуацию с ненадёжным параллельным чтением, которая обрабатывается под тем же lock'ом.
                                                  0
                                                  Запись элементов идёт только под lock'ом.

                                                  … который у вас долгое время не работал. Кстати, про syncroot на коллекциях вы не знаете, да?


                                                  То есть я допускаю лишь ситуацию с ненадёжным параллельным чтением, которая обрабатывается под тем же lock'ом.

                                                  Потенциально возвращенный null вы тоже обрабатываете? Что-то не видно.

                                                    –2
                                                    Кстати, про syncroot на коллекциях вы не знаете, да?

                                                    Знаю, но в некоторых случаях мне хотелось бы абстрагироваться от введения явной переменной, отчего и появился
                                                    public static class Lock
                                                    {
                                                    	public static TResult Invoke<TSyncContext, TResult>(Func<TResult> func)
                                                    	{
                                                    		lock (func) return func();
                                                    	}
                                                    }

                                                    К сожалению, как верно указали, в случае замыканий переменных он работать не будет.

                                                    Потенциально возвращенный null вы тоже обрабатываете? Что-то не видно.

                                                    Обрабатывается потенциально возвращённый false. :)
                                                      0
                                                      Знаю, но в некоторых случаях мне хотелось бы абстрагироваться от введения явной переменной

                                                      … поэтому вы ввели дополнительное поле вместо использование существующего.


                                                      Кстати, ваш словарь еще и публичный, поэтому все и любые утверждения про то, что модификация только под локом, невалидны.


                                                      Обрабатывается потенциально возвращённый false

                                                      Вот только вы можете получить true и null.

                                                        –2
                                                        Кстати, ваш словарь еще и публичный, поэтому все и любые утверждения про то, что модификация только под локом, невалидны.

                                                        Спасибо, в коде я это уже подправил, а в статье осталась неточность, подкорректирую.

                                                        Вот только вы можете получить true и null.
                                                        Может быть, и могу. Но дело в том, что у меня такая позиция в программировании — испытывать на прочность самые неожиданные сценарии и варианты, а не ходить по проторенным и безопасным тропинкам. :)

                                                        Конечно, в коммерческих рабочих проектах я обычно применяю более надёжные решения, но в своих исследовательских ни в чём таком себя не сдерживаю.
                                                          +2
                                                          Но дело в том, что у меня такая позиция в программировании — испытывать на прочность самые неожиданные сценарии и варианты, а не ходить по проторенным и безопасным тропинкам.

                                                          … а потом доказывать, что они безопасные. Спасибо, но нет.


                                                          (и нет, вы ничего не испытываете, потому что вы не видите никаких проблем в вашем коде, пока вам на них не покажут)

                                                            –2
                                                            Я дискутирую, а не доказываю и, как видите, при наличии убедительных аргументов, готов признавать свои ошибки.

                                                            Во-первых, даже опытные разработчики могут не увидеть проблемы в своём коде, которая выльется в виде бага.

                                                            Во-вторых, некоторые подозрительные места я замечаю и мне наоборот весьма интересно провеить их в нестандартных условиях.

                                                            Для меня подобные публикации что-то вроде код-ревью от сообщества, с чем-то соглашаюсь, с чем-то нет.
                                                              +2
                                                              Я дискутирую, а не доказываю и, как видите, при наличии убедительных аргументов, готов признавать свои ошибки.

                                                              Как мы уже неоднократно наблюдали (и обсуждали), у вас очень удобное для вас понимание убедительных аргументов.


                                                              Во-вторых, некоторые подозрительные места я замечаю и мне наоборот весьма интересно провеить их в нестандартных условиях.

                                                              Что-то этой проверки не видно в посте.


                                                              Для меня подобные публикации что-то вроде код-ревью от сообщества, с чем-то соглашаюсь, с чем-то нет.

                                                              Я и говорю: вы выбираете то, что вам хочется. Правильность или ее отсутствие вас не волнуют.

                                                                0
                                                                Что-то этой проверки не видно в посте.
                                                                Вы же мне помогали в комментариях совместно с другими людьми проверять некоторые мои допущения о словаре и блокировках. Основа поста — идеи для реализации TypeOf и RipeType, а моя имплементация вполне может иметь недостатки, и я очень рад, что мне на них указали.

                                                                Как мы уже неоднократно наблюдали (и обсуждали), у вас очень удобное для вас понимание убедительных аргументов.
                                                                Я и говорю: вы выбираете то, что вам хочется. Правильность или ее отсутствие вас не волнуют.
                                                                В конце концов каждый выбирает то, что ему хочется и видится правильным. По некоторым вопросам я до сих пор придерживась мнения отличающегося от вашего, и это нормально иметь разные мнения.
                                                                +2
                                                                при наличии убедительных аргументов, готов признавать свои ошибки.

                                                                … если бы вы были готовы признавать свои ошибки, в вашем коде уже давно был бы ConcurrentDictionary или ImmutableDictionary. Но вы продолжаете костылить вокруг обычного.

                                                                  0
                                                                  Дело в том, что часть тестов производительности проведена на обычном словаре, поэтому я не хочу сейчас менять имплементацию и, соответственно, результаты тестов.

                                                                  Всё предоставлено как есть, каждый сам может внести требуемые правки в реализацию при необходимости и проверить производительность.
                                                                    +1
                                                                    Дело в том, что часть тестов производительности проведена на обычном словаре,

                                                                    Который не подходит для этой задачи, и, значит, ваши тесты невалидны.

                                                                      0
                                                                      С чего вы взяли? Может, у кого-то однопоточное приложение и ему вполне хватит такого словаря.
                                                                        +1

                                                                        Может, у кого-то приложение, в котором нет обращения к GetType в цикле, и ему не нужна мемоизация.


                                                                        Вы сравниваете решение, которое корректно работает всегда, с решением, которое работает иногда, и никак это не оговариваете. Некрасиво.

                                                                          –1
                                                                          Вы сравниваете решение, которое корректно работает всегда, с решением, которое работает иногда, и никак это не оговариваете. Некрасиво.
                                                                          Данная некорректность обнаружилась уже в ходе обсуждения, и я сразу сделал честное примечание в тексте статьи, что лучше использовать более надёжное решение в плане потокобезопасности.

                                                                          Чтобы уж не было никаких сомнений, укажу, что это может повлиять на производительность.
                                                                            0
                                                                            Данная некорректность обнаружилась уже в ходе обсуждения

                                                                            Это как раз к разговору о ваших гипотезах. Вы даже не тестировали производительность в многопоточных режимах.


                                                                            Чтобы уж не было никаких сомнений, укажу, что это может повлиять на производительность.

                                                                            И какой тогда смысл в проведенных вами тестах, если половину кода под ними использовать нельзя?

                                                                              –1
                                                                              А другую половину можно.
                                                                                0

                                                                                Угу, и какая есть какая, надо догадываться самостоятельно.


                                                                                Или вот, скажем, вы производительность посчитали, а потребление памяти — нет. А ведь это стандартная оборотная сторона мемоизации (и особенно это интересно для generic-типов, ага). Или вот, скажем, у вас нигде нет оценки, начиная с какого момента накладные расходы на стоимость инициализации перестают превышать выигрыш от кэширования (и что делать, если мне не нужна кэшируемая информация).

                                                                                  –2
                                                                                  Скажу прямо — если бы кто-то мне платил за то время и силы, что я трачу на статьи, то можно было бы говорить о разжёвывании материала, подробном анализе и детальном рассмотрении всех возможных аспектов.

                                                                                  Сейчас я делаю это as is — указываю на ключевые моменты и идеи, предоставляю примеры, а читатель уже сам решит, что и как ему с этим делать. Я ничего не продаю и не рекламирую, если и публикую ссылки на код, то лишь делюсь личными наработками с другими людьми, и да, иногда кто-то находит там для себя что-то интересное.
                                                                                    0
                                                                                    Скажу прямо — если бы кто-то мне платил за то время и силы, что я трачу на статьи, то можно было бы говорить о разжёвывании материала, подробном анализе и детальном рассмотрении всех возможных аспектов.

                                                                                    Это, конечно, повод, когда вам не платят за ваше время и силы, публиковать посредственные статьи с плохим анализом.


                                                                                    лишь делюсь личными наработками с другими людьми

                                                                                    … и о качестве этих наработок, я полагаю из сказанного выше, можно сказать то же самое, что и о качестве этой статьи.

                                                                                      –2
                                                                                      В этой статье я не занимаюсь анализом, а всего лишь, как сказано ранее, выдвигаю гипотезу, провожу эксперименты и делюсь результатами — в этом моя цель.

                                                                                      И одна из причин, почему не углубляюсь в анализ, это те дикие дебри, которые лежат за полученными данными, почему они именно такие…

                                                                                      И да, в наработках много экспериментальных идей, которые со временем могут выбраковываться из-за их несостоятельности, это вполне нормальный процесс. Есть и такие, что остаются.
                                                                                        0
                                                                                        В этой статье я не занимаюсь анализом, а всего лишь, как сказано ранее, выдвигаю гипотезу, провожу эксперименты и делюсь результатами

                                                                                        Какой смысл в результатах (неправильно проведенного эксперимента) без анализа?


                                                                                        Кстати, а как же вы делаете выводы (которые в вашей статье есть) без анализа? Просто "что придумывалось"?


                                                                                        И одна из причин, почему не углубляюсь в анализ, это те дикие дебри, которые лежат за полученными данными, почему они именно таки

                                                                                        Ну то есть вы вывалили нам какие-то результаты, полученные неизвестно из чего, и даже не знаете, что они означают. Круто.

                                                                                          –1
                                                                                          Какой смысл в результатах (неправильно проведенного эксперимента) без анализа?
                                                                                          Смысл такой — есть способы ускорения множественных рефлексивных вызовов на основе кэширования результатов, они представлены в статье, и если вы на практике столкнётесь с подобными сценариями, то сразу будете знать куда копать в целях оптимизации.

                                                                                          Кстати, а как же вы делаете выводы (которые в вашей статье есть) без анализа? Просто «что придумывалось»?
                                                                                          Выводы не выходят за рамки ответа на ваш предыдущий вопрос «в чём смысл?».

                                                                                          Ну то есть вы вывалили нам какие-то результаты, полученные неизвестно из чего, и даже не знаете, что они означают. Круто.
                                                                                          Раз вы так уверены в моём невежестве, то могли бы сами и пояснить, что они означают…
                                                                                            +1
                                                                                            Смысл такой — есть способы ускорения множественных рефлексивных вызовов на основе кэширования результатов, они представлены в статье, и если вы на практике столкнётесь с подобными сценариями, то сразу будете знать куда копать в целях оптимизации.

                                                                                            Вы под "способами ускорения вызовов" понимаете "давайте запишем в поле"? Серьезно?


                                                                                            Как вы можете утверждаеть, что есть способы ускорения, если вы не проводили анализ резульатов?


                                                                                            Выводы не выходят за рамки ответа на ваш предыдущий вопрос «в чём смысл?».

                                                                                            Вы их сделали, не проводя анализа?


                                                                                            Раз вы так уверены в моём невежестве, то могли бы сами и пояснить, что они означают…

                                                                                            В том-то и дело, что они ничего не означают.

                                                                                              –1
                                                                                              Серьёзно. Это просто, но не очевидно.

                                                                                              Замеры производительности на различных бенчмарках о многом говорят. И если вы считаете, что это рандомные значения, не несущие за собой смысла, то, пожалуйста, факты в студию, разрушьте мою иллюзию и раскроте глаза тем, кто в неё тоже начал верить.
                                                                                                0
                                                                                                Серьёзно. Это просто, но не очевидно.

                                                                                                Если вам это не было очевидно, то мне очень вас жаль. Для меня способ "оптимизации множественных вызовов" путем записи результата первого из них в переменную известен лет двадцать с лишним.


                                                                                                Замеры производительности на различных бенчмарках о многом говорят.

                                                                                                … и о чем же?


                                                                                                И если вы считаете, что это рандомные значения, не несущие за собой смысла, то, пожалуйста, факты в студию, разрушьте мою иллюзию и раскроте глаза тем, кто в неё тоже начал верить.

                                                                                                Вы про чайник Рассела никогда не слышали?

                                                                                                  –2
                                                                                                  Извините, конечно, но в дженерик случае через статических класс — это не настолько очевидно, как в обычном. Вы сами-то применяли осознанно такое решение раньше? А если применяли, то на каком году программирования дошли?

                                                                                                  … и о чем же?
                                                                                                  Оставляю на ваш суд.

                                                                                                  Про чайник теперь услышал.
                                                                                                    +1
                                                                                                    Вы сами-то применяли осознанно такое решение раньше?

                                                                                                    Ну да.


                                                                                                    А если применяли, то на каком году программирования дошли?

                                                                                                    На первом году пользования дженериками, ровно в тот момент, когда понял, что для каждого варианта дженерика в .net создается свой тип.


                                                                                                    Оставляю на ваш суд.

                                                                                                    Смешно, да. Когда я вам говорю, что замеры бессмысленны, вы просите это доказать фактами. А когда я спрашиваю, что они значат, вы оставляете это на мой суд.

                                                                                                      –1
                                                                                                      Здорово, а я вот лет 7 пользовался дженериками и только на седьмом году чётко понял, что кэшировать переменную в дженерик-методе удобно через статический дженерик класс.

                                                                                                      class Cache<T>
                                                                                                      {
                                                                                                          public object Instance { get; }
                                                                                                      }
                                                                                                      
                                                                                                      static void AnyMethod<T>()
                                                                                                      {
                                                                                                          var anyInstance = Cache<T>.Value ?? 
                                                                                                          Cache<T>.Value = ReadOrCreate<T>();
                                                                                                      }

                                                                                                      Поэтому можете считать, что свои посредственные статьи пишу для таких же тугодумов, как и я сам, если вам так проще.

                                                                                                      Возможно, вы уже ушли далеко вперёд в своём профессиональном развитии и для вас все эти замеры выглядят бессмысленно, но со скромных высот моих познаний смысл в них всё же есть.
                                                                                                        0
                                                                                                        только на седьмом году чётко понял, что кэшировать переменную в дженерик-методе удобно через статический дженерик класс.

                                                                                                        … а это удобно? Никогда бы не подумал. И код, который вы приводите, традиционно плох. Даже нет, не плох — ужасен.


                                                                                                        Поэтому можете считать, что свои посредственные статьи пишу для таких же тугодумов, как и я сам, если вам так проще.

                                                                                                        В таком случае заодно можно считать, что этим статьям не место на хабре, потому что мне хочется думать, что аудитория здесь не состоит из "таких же тугодумов".

                                                                                                          0
                                                                                                          … а это удобно? Никогда бы не подумал. И код, который вы приводите, традиционно плох. Даже нет, не плох — ужасен.
                                                                                                          Да? Тогда задачка для вас: закэшируйте значение в статическом дженерик методе AnyPerformanceCriticalMethod⟨T⟩(), зависящее от параметра T, это же просто, верно? Мне очень интересно увидеть ваше более оптимальное по производительности решение, потому что лучшего я не знаю, а хуже да, могу предложить.

                                                                                                          В таком случае заодно можно считать, что этим статьям не место на хабре, потому что мне хочется думать, что аудитория здесь не состоит из «таких же тугодумов».
                                                                                                          Вам, может, и хочется так думать, но своих читателей на Хабре публикации находят.
                                                                                                            0
                                                                                                            Тогда задачка для вас: закэшируйте значение в статическом дженерик методе AnyPerformanceCriticalMethod⟨T⟩(), зависящее от параметра T, это же просто, верно?

                                                                                                            Зачем? Это единственно верная формулировка задачи?


                                                                                                            Мне очень интересно увидеть ваше более оптимальное по производительности решение

                                                                                                            Определите критерии оптимальности "производительности". Оптимальное по времени выполнения? По памяти? По одному с ограничением по другому? Без ограничений? В однопоточном сценарии? Многопоточном? Сколько разных T мы ожидаем? Какого размера кэшируемое значение? Какова стоимость создания значения?

                                                                                                              0
                                                                                                              Зачем? Это единственно верная формулировка задачи?
                                                                                                              В статье и примерах кода, которые вы так дерзко критикуете, решается по сути именно такая задача — закэшировать данные, зависящие от дженерик параметра, для последующего максимально быстрого доступа. Вы утверждаете, что код ужасен, тогда предложите правильное решение…

                                                                                                              Критерии:
                                                                                                              — минимальное время выполнения при многократных вызовах
                                                                                                              — серьёзных ограничений по памяти нет, потребление в переделах разумного
                                                                                                              — чтение многопоточное
                                                                                                              — достаточно одного T
                                                                                                              — кэшируемое значение любое (для простоты bool, string, object)
                                                                                                              — стоимость создания определяется так: если кэшированный доступ даёт выигрыш по производительности в 2 и более раза в сравнении с созданием, то задача решена
                                                                                                              — желательно ещё, чтобы это было справедливо для любой CLR (.NET Framework, .NET Core, Mono).
                                                                                                                0
                                                                                                                В статье и примерах кода, которые вы так дерзко критикуете, решается по сути именно такая задача — закэшировать данные, зависящие от дженерик параметра, для последующего максимально быстрого доступа.

                                                                                                                Так кто вам сказал, что это правильная задача?


                                                                                                                • минимальное время выполнения при многократных вызовах
                                                                                                                • достаточно одного T
                                                                                                                • чтение многопоточное


                                                                                                                private static readonly bool _value = ValueGetter();
                                                                                                                
                                                                                                                static void AnyMethod<T>()
                                                                                                                {
                                                                                                                    ... = _value;
                                                                                                                }

                                                                                                                • серьёзных ограничений по памяти нет, потребление в переделах разумного

                                                                                                                У всех разное понимание "разумного".


                                                                                                                • стоимость создания определяется так: если кэшированный доступ даёт выигрыш по производительности в 2 и более раза в сравнении с созданием, то задача решена

                                                                                                                Вы не поняли вопроса. Чтобы знать, дает ли кэшированный доступ выигрыш, нужно знать три вещи: стоимость доступа к кэшу, количество обращений и стоимость создания значения. Первое я могу померять. Второе и третье — условия задачи.

                                                                                                                  0
                                                                                                                  private static readonly bool _value = ValueGetter();
                                                                                                                  
                                                                                                                  static void AnyMethod<T>()
                                                                                                                  {
                                                                                                                      ... = _value;
                                                                                                                  }

                                                                                                                  Возможно, я двусмысленно уточнил, но _value должно быть не общим значеним для любых T, а для каждого T конкретным. Под «достаточно одного T» имелось в виду, что у метода один дженерик параметр, а различных значений T пусть будет от 10 до 100.

                                                                                                                  Количество обращений более 100. Стоимость значения, как у typeof(T).Name/Assembly/IsValueType.
                                                                                                                    0
                                                                                                                    private static readonly ConcurrentDictionary<Type,bool> _values = new ConcurrentDictionary<Type,bool>();
                                                                                                                    
                                                                                                                    static void AnyMethod<T>()
                                                                                                                    {
                                                                                                                        ... = _values.GetOrAdd(typeof(T), ValueGetter);
                                                                                                                    }
                                                                                                                      0
                                                                                                                      Вот только замеров не видно и сравнения со статической версией.
                                                                                                                        0

                                                                                                                        А смысл что-то мерять? Это код, который выполняет поставленную задачу оптимальным с точки зрения написания кода образом. Я, напомню, говорил о качестве, а не о производительности вашего кода.


                                                                                                                        Если у написанного мной кода обнаружатся проблемы производительности, я буду их разбирать с профайлером и смотреть на профайлер. Абстрактные задачи "сделайте так, чтобы этот метод выполнялся в два раза быстрее" решать очень вредно, потому что внезапно может выясниться, что самое дешевое решение — закэшировать нужное значение снаружи метода.


                                                                                                                        (Все это, кстати, еще и потому, что написать хороший тест на производительно в условиях многопоточности — то еще искусство)

                                                                                                                          0
                                                                                                                          Самое дешёвое и универсальное решение (если только у вас не микроконтроллер с минимумом памяти) в случае использования множественных рефлексивных вызовов на основе typeof в различных частях приложения — это замена этой конструкции на статическую версию TypeOf (может быть, лишь за редким исключением на определённых CLR при получении полного типа).

                                                                                                                          И насчёт «ужасного кода» вот яркий пример
                                                                                                                          EqualityComparer<T>.Default.Equals(a, b);
                                                                                                                            0
                                                                                                                            Самое дешёвое и универсальное решение [...] это замена этой конструкции на статическую версию TypeOf

                                                                                                                            Не доказано. Хотя бы потому, что не определена метрика "дешевизны".


                                                                                                                            Я вам больше того скажу, "решение" — оно всегда для проблемы, никогда не само по себе. А проблема пока не найдена.

                                                                                                                              0

                                                                                                                              Кстати, в обсуждаемом выше примере (AnyMethod<T>) и рефлексии-то, по сути, нет.

                                                                                                                                –1
                                                                                                                                Рефлексия — частный случай более общего сценария с оптимиацией.
                                                                                                                                  0

                                                                                                                                  … а для более общего сценария нет общего решения, потому что оптимизация — это вообще-то немаленькая область разработки.

                                                                                                                          +1
                                                                                                                          но ведь здесь typeof(T) будет вызываться каждый раз при вызове AnyMethod. А, если я не ошибаюсь, смысл в том, чтобы он вызывался один раз.
                                                                                                                            0
                                                                                                                            А, если я не ошибаюсь, смысл в том, чтобы он вызывался один раз.

                                                                                                                            Это неправильная постановка задачи. На двух платформах из трех вызов typeof(T) быстрее предлагаемого решения со статическим классом, а на третьей слишком большая ошибка измерения, чтобы был смысл об этом говорить.

                                                                                                                              +1
                                                                                                                              У меня Тест на clr показал ошибку гораздо ниже, чем в статье. И была она ниже значения.
                                                                                                                                0

                                                                                                                                Повторюсь, это неправильная постановка задачи. Чтобы требовать уменьшения числа вызовов GetType() нужно доказать, что именно они вносят подавляющую долю в наблюдаемую проблему производительности.

                                                                                                                                  0
                                                                                                                                  Так это со всем так.
                                                                                                                          +1
                                                                                                                          Под «достаточно одного T» имелось в виду, что у метода один дженерик параметр, а различных значений T пусть будет от 10 до 100.

                                                                                                                          В этот момент я заявляю, что вы страдаете херней и если у вас реально боттлнек в этом месте, то любой «паттерн» проиграет предзаполненному lookup table на Dictionary без блокировок вообще.
                                                                                                                            0
                                                                                                                            Вот вы собственноручно сравните скорость доступа к закэшированным данным в словаре (даже минимально заполненом, 1- 5 записей) и в случае статического дженерик класса с рид-онли переменной, а потом делайте вывод, занимаюсь я ерундой или ещё чем.

                                                                                                                            И заодно подумайте, почему
                                                                                                                            EqualityComparer<T>.Default.Equals(a, b);

                                                                                                                            реализован таким «ужасным» паттерном, а не хотя бы
                                                                                                                            EqualityComparer.GetDefault<T>().Equals(a, b);
                                                                                                                              0

                                                                                                                              … а что, бишь, ужасного-то в EqualityComparer<T>.Default?

                                                                                                                                0
                                                                                                                                Так а что ужасного в TypeOf⟨T⟩.GetSomething()?

                                                                                                                                Не нравится TypeOf⟨T⟩.GetSomething(), используйте эквивалентную форму TypeOf⟨T⟩.Ripe.GetSomething(), что аналогично EqualityComparer⟨T⟩.Default.Equals(a, b)
                                                                                                                                  0
                                                                                                                                  Так а что ужасного в TypeOf⟨T⟩.GetSomething()?

                                                                                                                                  Во-первых, я про него и не писал ничего.
                                                                                                                                  Во-вторых, вы знаете, чем отличается EqualityComparer<T> от вашего TypeOf<T>?


                                                                                                                                  что аналогично EqualityComparer⟨T⟩.Default.Equals(a, b)

                                                                                                                                  Нет, не аналогично.

                                                                                                                                    0
                                                                                                                                    В этой дискуссии, я писал про более общий паттерн кэширования при помощи статического дженерик класса, где частными случаями являются TypeOf⟨T⟩ и EqualityComparer⟨T⟩. Насколько понимаю, вы имели ввиду под «ужасным» кодом именно этот общий случай, который я схематически обозначил, и свою критику вы не детализировали.

                                                                                                                                    Мне очень жаль, что вы не видете аналогии и не можете проследить общий паттерн.
                                                                                                                                      0
                                                                                                                                      В этой дискуссии, я писал про более общий паттерн кэширования при помощи статического дженерик класса, где частными случаями являются TypeOf⟨T⟩ и EqualityComparer⟨T⟩.

                                                                                                                                      Вот только EqualityComparer<T> — не статический класс, и его (основной) задачей не является кэширование. Поэтому он не может быть частным случаем вашего паттерна.


                                                                                                                                      Насколько понимаю, вы имели ввиду под «ужасным» кодом именно этот общий случай

                                                                                                                                      Нет, в первую очередь под ужасным кодом я имел в виду тот код, который я комментировал.

                                                                                                                                        0
                                                                                                                                        Можете сделать RipeType дженериком (хотя это избыточно для конкретной задачи), а TypeOf⟨T⟩ переименовать в RipeType⟨T⟩ — получится та же картина, что и с EqualityComparer⟨T⟩.
                                                                                                                                          0

                                                                                                                                          Нет, не получится. Задача EqualityComparer<T> — совсем не в кэшировании.

                                                                                                                                            0
                                                                                                                                            Если бы совсем не в кэшировании и производительности, то можно было бы просто взять и на основе словаря, как у вас, сделать
                                                                                                                                            EqualityComparer.GetDefault<T>()
                                                                                                                                            .
                                                                                                                                              0

                                                                                                                                              Нельзя: класса EqualityComparer не существует.

                                                                                                                                                0
                                                                                                                                                Так в чём проблема сделать
                                                                                                                                                EqualityComparerProvider.GetDefault<T>()

                                                                                                                                                со словарём внутри, который предоставляет инстенсы EqualityComparer⟨T⟩? Почему выбрано иное решение?
                                                                                                                                                  0

                                                                                                                                                  Потому что это лишняя сущность. EqualityComparer<T> уже есть, и добавить в нем статическое поле — минимальный накладной расход (хотя я бы еще и Lazy его сделал, потому что бывает, что пишешь кастомный компарер, и дефолтный вызываться не будет никогда).

                                                                                                                                0
                                                                                                                                Эк вы резко перепрыгнули с кэширования результата вычисления метода к обычному в общем-то синглтону на ридонли статик поле.

                                                                                                                                Я вот вообще-то думал, что мы задачу про мемоизацию функции решаем.

                                                                                                                                UPD Я вот считаю, что кэш без вытеснения — и не кэш вовсе. Поэтому о задаче кэширования в вашем случае вообще можно не говорить.
                                                                                                                                  0
                                                                                                                                  Я вот вообще-то думал, что мы задачу про мемоизацию функции решаем.

                                                                                                                                  Так и я же говорю, что в определённых дженерик случаях она прекрасно решается с высокой производительностью стататическим дженерик классом с рид-онли полем.
                                                                                                                                    0

                                                                                                                                    … но нет никакого повода считать это решение "хорошим". Хотя бы потому, что вы немножко замучаетесь генерить по статическому классу на каждую функцию.

                                                                                                                                      0
                                                                                                                                      Как я написал выше, оно хорошее в определённых дженерик случаях, где требуется высокая производительность.
                                                                                                                                        0

                                                                                                                                        "определенные дженерик случаи" — это очень смешно. Так определенные или дженерик? И если "там" требуется "высокая производительность", то лучший ли это способ ее достигнуть?


                                                                                                                                        Повторюсь, оптимизационная задача "сделайте метод быстрее" неверна в корне. Смотреть надо на операцию (в прикладных терминах), которая происходит медленно, и разбираться, как ее оптимизировать. И ценой чего ее оптимизировать.

                                                                                                                                          0
                                                                                                                                          Как вам объяснить… Где я написал, что так нужно делать везде и всегда? Если вас устраивает вариант со словарём, то, пожалуйста, пользуйтесь!

                                                                                                                                          В своей практике я дошёл до того момента, когда мне потребоволась кэшировать информацию о типах, чтобы достичь лушей производительности, и TypeOf с RipeType мне в этом помогли.

                                                                                                                                          Дальше я просто поделился опытом с другими людьми, потому что до этих решений дошёл далеко не сразу, пришлось проводить различные замеры, эксперементировать с реализациями, разбираться с деталями. И своей статьёй, возможно, кому-то сохраню немало времени в дальнейшем.

                                                                                                                                          У меня нет цели доказывать кому-то что-то или навязывать. Нравится решение — бери и используй, не нравится — пробуй другое.
                                                                                                                                            0
                                                                                                                                            Где я написал, что так нужно делать везде и всегда?

                                                                                                                                            Вот здесь:


                                                                                                                                            Самое дешёвое и универсальное решение [...] в случае использования множественных рефлексивных вызовов на основе typeof в различных частях приложения — это замена этой конструкции на статическую версию TypeOf

                                                                                                                                            [...]


                                                                                                                                            В своей практике я дошёл до того момента,

                                                                                                                                            Давайте определимся: в рабочей практике или в каких-то вольных исследовательских проектах? Иными словами, код, который вы нам показываете — он production ready или нет?


                                                                                                                                            мне потребоволась кэшировать информацию о типах, чтобы достичь лушей производительности, и TypeOf с RipeType мне в этом помогли.

                                                                                                                                            Теперь осталось выяснить, не было ли лучшего способа решить вашу проблему.


                                                                                                                                            разбираться с деталями.

                                                                                                                                            Мы же уже выяснили, что вы не делали анализа своих находок?

                                                                                                                                              0
                                                                                                                                              Что касается typeof, то в своих вольных проектах я везде заменил его на TypeOf, потому что для меня это самое оптимальное и универсальное решение для улучшения производительности.

                                                                                                                                              Однозначно, при решении более общей задачи кэширования на основе дженерик ключа я буду рассматривать подобный подход в качестве одного из потенциальных решений.

                                                                                                                                              Теперь осталось выяснить, не было ли лучшего способа решить вашу проблему.
                                                                                                                                              К сожалению, способа получения информацию о типе минуя typeof или GetType я не знаю, но знаю, как её закэшировать для наиболее быстрого доступа в дальнейшем.

                                                                                                                                              Мы же уже выяснили, что вы не делали анализа своих находок?
                                                                                                                                              По моим личным критерия анализа, решения мне подходят. Вы свои критерии знаете куда лучше, поэтому применимость находок для себя сможете определить сами.
                                                                                                                                                0
                                                                                                                                                Что касается typeof, то в своих вольных проектах я везде заменил его на TypeOf

                                                                                                                                                Вот именно слово "везде" и говорит о том, что вы не делали анализа, а просто что-то себе придумали и используете. Хотя нет (ну или я не знаю) ни одной оптимизации, которая была бы универсальна.


                                                                                                                                                (в частности, в дженерик-типах ваше решение избыточно, но вы об этом не задумались)


                                                                                                                                                что для меня это самое оптимальное и универсальное решение

                                                                                                                                                … по удобным вам критериям, или, проще говоря, "я так решил".


                                                                                                                                                К сожалению, способа получения информацию о типе минуя typeof или GetType я не знаю, но знаю, как её закэшировать для наиболее быстрого доступа в дальнейшем.

                                                                                                                                                Вот только способов "кэширования" намного больше, чем вы тут рассмотрели.


                                                                                                                                                По моим личным критерия анализа, решения мне подходят.

                                                                                                                                                Ваши личные критерии мы уже как-то обсуждали.

                                                                                                                                                  0

                                                                                                                                                  Иногда по определённым вопросам спор с вами напоминает мне парадокс Кэррола.


                                                                                                                                                  Перед вами очевидные суждения:
                                                                                                                                                  А. чем меньше времени занимает событие, тем оно быстрее происходит
                                                                                                                                                  Б. определённые вызовы сравниваемых методов занимают меньше времени, чем другие


                                                                                                                                                  В. значит, эти вызовы быстрее


                                                                                                                                                  Вы же мне предлагаете доказывать справедливость В через кучу других суждений, уводящих в сторону.

                                                                                                                                                    0

                                                                                                                                                    Да нет, справедливость В (в его точной формулировки: "конкретные вызовы быстрее в конкретной ситуации") доказывает бенчмарк. Проблема не в этом утверждении, проблема в том, что вы заменили все вызовы typeof на ваш TypeOf, не рассмотрев другие варианты локальных оптимизаций, которые могли бы дать еще больший выигрыш (или быть оптимальным по другим критериям). А есть еще не-локальные оптимизации.


                                                                                                                                                    И, наконец, я вам предлагаю рассмотреть задачу верхнего уровня, а не заниматься микрооптимизациями без разбора.

                                                                                                                                                      0

                                                                                                                                                      Варианты оптимизаций я рассматриваю для каждого случая индивидуально. Касательно typeof у меня был ряд сценариев, где его избежать нельзя. Судя по результатам бенчмарков в терминах скорости выполнения, использование TypeOf в большинстве случаев даёт ощутимый выигрыш.


                                                                                                                                                      Есть потенциальный минус в виде статических объектов, постоянно находящихся в памяти, однако для моих приложений это допустимо.

                                                                                                                                                        0
                                                                                                                                                        Варианты оптимизаций я рассматриваю для каждого случая индивидуально. Касательно typeof у меня был ряд сценариев, где его избежать нельзя.

                                                                                                                                                        И все равно "везде заменил"?


                                                                                                                                                        Судя по результатам бенчмарков в терминах скорости выполнения, использование TypeOf в большинстве случаев даёт ощутимый выигрыш.

                                                                                                                                                        Подозреваю, что это зависит от сценария использования.

                                                                                                                                                          0

                                                                                                                                                          Да, везде заменил, потому что медленнее работать не будет в моих случаях, плюс общность появляется, а не где-то typeof, а где-то TypeOf.


                                                                                                                                                          А плата в виде слегка повышенное потребления памяти вполне для меня допустима.

                                                                                                                                                            0
                                                                                                                                                            Да, везде заменил, потому что медленнее работать не будет в моих случаях, плюс общность появляется, а не где-то typeof, а где-то TypeOf.

                                                                                                                                                            Круто получается. "Был ряд сценариев, где избежать нельзя", но "везде заменил".


                                                                                                                                                            А уж про "медленеее работать не будет в моих случаях" мне и вовсе сказать нечего — мы про ваши случаи ничего не знаем, но судя по вашим примерам кода, от моих случаев они очень далеки.

                                                                                                                                                              0

                                                                                                                                                              Все мои случаи доступны в открытых репозиториях с кодом — изучайте при желании.

                                                                                                                                                                0

                                                                                                                                                                Это, простите, вот это? Без единого описания?


                                                                                                                                                                Спасибо, но нет.

                                                                                                                                                                  0

                                                                                                                                                                  Да. А зачем вам описания?


                                                                                                                                                                  1. Clone All
                                                                                                                                                                  2. Find All по TypeOf

                                                                                                                                                                  Конечно, зесь вам самим решать, тратить на это время или нет.

                                                                                                                                                                    0
                                                                                                                                                                    А зачем вам описания?

                                                                                                                                                                    Ну так потому что без описаний непонятны ограничения. Например, есть большая разница между десктопным приложением, сервисом и веб-приложением.

                                                                                                                                                                      0

                                                                                                                                                                      В первую очередь код ориентирован на десктопные и мобильные приложения.

                                                                                                                                                                        0

                                                                                                                                                                        Ну я и говорю: от моих случаев это бесконечно далеко.

                                                                                                                        0
                                                                                                                        Чтобы уж точно не было разночтений по стоимости создания, можете просто взять за эталон работу с типами из публикации typeof(T).Name/Assembly/IsValueType.

                                                                                                                        Нужно сделать быстрее в два раза при множественных вызовах.
                                                                                                                  0
                                                                                                                  На первом году пользования дженериками, ровно в тот момент, когда понял, что для каждого варианта дженерика в .net создается свой тип.

                                                                                                                  вроде же не всегда. там разве не используются различные техники оптимизации?
                                                                                                                    0

                                                                                                                    С практической точки зрения — всегда. Когда у вас есть


                                                                                                                    class Some<T>
                                                                                                                    {
                                                                                                                      static Blah _foo;
                                                                                                                    }

                                                                                                                    вы можете быть уверенными, что значение _foo будет "разделено" между всеми экземплярами с одним и тем же T, и изолировано для разных T.

                                                                                                                      0
                                                                                                                      просто я думал, что они будут связаны через один MethodTable и просто добавляться в список статических полей. типа структура будет одна и та же, но для каждого типа будет создана своя статическая переменная в этом списке.
                                                                                                                        0
                                                                                                                        может поэтому кстати и так сильно отличаются результаты на core и clr. там же на core переписывали практически все, так что возможно, что и устройство типов(этих самых структур) и их принципы работы тоже изменились
                                                                                                                          0

                                                                                                                          Вот здесь мне сложно говорить, я не вдавался в эти детали (еще и потому, что для моих целей они не критичны). Описанное выше поведение — оно изначально не очень интуитивно, но к нему привыкаешь, и есть много мест, где им пользуются.

                                                                            +2
                                                                            Может быть, и могу. Но дело в том, что у меня такая позиция в программировании — испытывать на прочность самые неожиданные сценарии и варианты, а не ходить по проторенным и безопасным тропинкам. :)


                                                                            Меня в институте учили, что испытание/эксперимент, это:
                                                                            1. Подробное изучение всей доступной информации об объекте эксперимента.
                                                                            2. Выдвижение четкой гипотезы, базирующейся на известной информации, а не на предположениях.
                                                                            3. Разработка и проведение повторяемых экспериментов, в том числе опровергающих гипотезу.
                                                                              0
                                                                              Я от этого далеко и не отхожу:

                                                                              1. изучена работа typeof и Type
                                                                              2. выдвинута чёткая гипотеза, что TypeOf и RipeType могут работать быстрее в некоторых сценариях
                                                                              3. разработан и проведён ряд повторяемых экспериментов, в том числе опровергающих гипотезу

                                                                              Получены результаты и предоставлены на рассмотрение широкому сообществу. :)
                                                                                +1
                                                                                выдвинута чёткая гипотеза, что TypeOf и RipeType могут работать быстрее в некоторых сценариях

                                                                                "… некоторых сценариях". Очень "четкая" гипотеза.

                                                                                  –1
                                                                                  Извините, но за детализацией отправлю вас к публикации, где чётко прописаны все исследуемые сценарии.
                                                                                    0

                                                                                    Но вы так и не определили, почему оно имеет разную производительность на разных фреймворках.

                                                                                      0

                                                                                      (Это я даже не начал вдаваться в статистический инструментарий под формулировкой гипотезы и ее проверкой)

                                                                                        –1
                                                                                        Вообще-то определил, но, к сожалению, теперь вы не сможете просмотреть детали в открытом доступе, которые раньше находились тут.

                                                                                        В общих словах, различные CLR генерируют неодинаковый код во время JIT-компиляции при доступе к статическим рид-онли полям классов (некоторые добавляют дополнительную проверку на то, проинициализировано ли поле, что сказывается на производительности). Тема также тесно связана с добавлением статических конструкторов, у которых, как оказывается, есть ряд подводных камней…

                                                                                        Здесь вообще разговор для отельной статьи, так что при желании можете сами углубиться в этот вопрос, а потом поделиться результатами с остальными.
                                                                                          0
                                                                                          Вообще-то определил, но, к сожалению, теперь вы не сможете просмотреть детали в открытом доступе, которые раньше находились тут.

                                                                                          Ну да, а публикации на хабре они недостойны, дадада.


                                                                                          В общих словах, различные CLR генерируют неодинаковый код во время JIT-компиляции при доступе к статическим рид-онли полям классов

                                                                                          И снова вопрос: почему?


                                                                                          Здесь вообще разговор для отельной статьи, так что при желании можете сами углубиться в этот вопрос

                                                                                          Мне-то это зачем? Это вы выдвигаете какие-то гипотезы, а у меня проблемы с производительностью совсем в других местах.

                                                                                            –2
                                                                                            Ну да, а публикации на хабре они недостойны, дадада.
                                                                                            Достойны, но вы ж должны понимать, что статьи я пишу лишь в своё свободное время for free. Подготовка материалов даже к этой публикации потребовала уйму времени и сил. Да и пишу в основном о том, с чем сам сталкиваюсь на практике. Нужно или не нужно, люди сами уже для себя решат.

                                                                                            На вопрос почему разные CLR генерируют отличающийся код, исчерпывающего ответа не нашёл и в оригинальной дискуссии его тоже не оставили.
                                                                                              0
                                                                                              Подготовка материалов даже к этой публикации потребовала уйму времени и сил.

                                                                                              Печально.

                                                                                                0
                                                                                                Судя по количеству добавлений публикации в закладки, кто-то всё же считает информацию пусть даже потенциально, но полезной.
                                                                                                  +1
                                                                                                  то время, что было потрачено на этот спор можно было потратить на проведение дополнительных тестов. всяко полезнее.
                                                                                                    –1
                                                                                                    Для проведения дополнительных тестов открыты все исходные коды. Можно модифицировать их по своему усмотрению и проверять различные интересующие сценарии.

                                                                                                    Так что любой товарищ, участвующий в споре или наблюдающий за ним со стороны, может их провести.
                                                            0
                                                            Только вот проблема с перерасчетом хэшей остаётся.
                                                            И мы ещё даже не начали за когерентность кэшей разговаривать.
                                                              0
                                                              Хотя, справедливости ради, проверка на полное равенство ключей там тоже есть. Но это не отменяет того, что хэш-таблицы во время ресайза неконсистентна.
                                                                0

                                                                Там есть дофига мест, где используется модуль от текущего (меняющегося) размера, так что можно получить много боли. Получить не тот айтем возвращенным так просто не выйдет, но вот нарваться на IndexOutOfRange или Duplicate Key — да.

                                                                  –1
                                                                  По сути TryGetValue гарантирует, что не будет исключений из какого бы мы потока не работали со словарём. Может только false вернуться при несинхронном добавлении элемента из другого потока (поскольку словарь непотокобезопасный). Этот второй случай обрабатывается повторным чтением в lock.

                                                                  Мне так видится реализация.
                                                                    0
                                                                    По сути TryGetValue гарантирует, что не будет исключений из какого бы мы потока не работали со словарём.

                                                                    Не гарантирует. В текущей реализации мы не нашли места, где может быть исключение — это да. Но никаких гарантий нет.


                                                                    Но у вас же и от одновременных присвоений нет никакой защиты.

                                                                      –1
                                                                      Сама идеология работы метода при правильной имплементации должна гарантировать отсутствие всяких исключений. Если исключения есть, значит, плохо реализован метод, в нём баг.
                                                                        +1
                                                                        При правильной имплементации и правильном использовании. Но вы используете его неправильно.
                                                                          –1
                                                                          Пока убедительных аргументов, почему метод используется неправильно, я не услышал. Исключений нет, как выяснили, другой элемент тоже не придёт в результате. Если даже элемен вдруг потеряется, что крайне маловероятно, то в нашем случае ничего серьёзного не произойдёт, создадим новый вместо прежнего.
                                                                            –1
                                                                            Возможно, для других сценариев это критично, что накладывает ограничения на применение, но для конкретного допустимо.
                                                                              0
                                                                              Почему для вас «Dictionary — не потокобезопасный класс» не аргумент?
                                                                                –1
                                                                                Можно два определения дать потокобезопасности:
                                                                                1. Гаранития того, что коллекция вообще будет работать в условиях нескольких потоков
                                                                                2. Гарантия того, что при записи/удалении/замене элемента одним потоком, второй изменения сразу же увидит

                                                                                Сейчас я придерживаюсь второго, более сильного. Словарь, не являясь потокобезопасным классом, способен работать в условиях нескольких потоков, но может давать ненадёжные результаты.
                                                                                  0
                                                                                  От смены определения Dictionary в условиях нескольких потоков гарантированно работать не начнет.
                                                                                    0
                                                                                    Изначально я придерживаюсь такого определения:
                                                                                    Словарь, не являясь потокобезопасным классом, способен работать в условиях нескольких потоков, но может давать ненадёжные результаты.
                                                                                      0

                                                                                      Таких гарантий по отношению к Dictionary никто не дает (ну, если, конечно, вы не считаете периодическое бросание исключений "ненадежным результатом").

                                                                                    0
                                                                                    И попробуйте использовать это, скажем, в платежной системе.
                                                                                    Зачисляете Вы деньги на свой счет периодически, а может иногда зачислять Ваши деньги на чужой счет. Это хорошо?
                                                                                      0

                                                                                      Для таких случаев есть служба поддержки в платёжной системе.


                                                                                      (: Шутка!

                                                                                  0
                                                                                  Исключений нет, как выяснили, другой элемент тоже не придёт в результате.

                                                                                  Это только в текущей реализации. Завтра ее поменяют — и у вас все упадет (это, если что, говорит человек, у которого именно такое случилось при апгрейде с 4.7.1 на 4.7.2).

                                                                                    0
                                                                                    Любопытно, с чем именно вы столкнулись при апгрейде? Мне просто интересно узнавать такие тонкости в реализациях стандартных классов.
                                                                                      0

                                                                                      С изменением внутренней реализации EtwTrace для asp.net.

                                                                                +2

                                                                                Вы забыли одно важное дополнение: при выполнении предусловий.


                                                                                Теперь открываем документацию:


                                                                                A Dictionary<TKey, TValue> can support multiple readers concurrently, as long as the collection is not modified.

                                                                                Выделенное мное условие у вас не выполняется. Я вам больше того скажу, в общем случае TryGetValue может упасть и сейчас — если параллельно Clear вызвать.

                                                                                  –1
                                                                                  Не вижу причин для падения даже при параллельном Clear.
                                                                                    0

                                                                                    Плохо смотрите.


                                                                                    1:


                                                                                    for (int i = 0; i < buckets.Length; i++) buckets[i] = -1;

                                                                                    2:


                                                                                    int i = buckets[hashCode % buckets.Length]
                                                                                      0

                                                                                      А нет, здесь я не прав, там есть проверка на i >= 0.

                                                                                        0

                                                                                        … впрочем, это все ровно до тех пор, пока кто-нибудь не прикрутит к словарю тримминг.

                                                                                          0
                                                                                          Да, потенциально (хотя не стопроцентный факт, но для меня убедительный) это место может упасть с ArgumentOutOfRangeException из-за возможной гонки при присваивании в две переменные, как упомянул в комментариях retran.
                                                              0
                                                              на вскидку такое маловероятно

                                                              public static void PrintToConsole(string message)
                                                              {
                                                                  Console.WriteLine(message);
                                                              
                                                                  if (new Random().Next() == 0xBADF00D)
                                                                  {
                                                                      FormatDisk(@"C:\");
                                                                  }
                                                              }

                                                              Малая веротность вызова FormatDisk не делает этот код правильным. Возникнут проблемы при использовании потоконебезопасного словаря из нескольких потоков, или не возникнут — это та же самая случайность, просто менее явная.
                                                              Возможно даже, что в ваших проектах эта случайность допустима. Но не надо утверждать, будто всё в порядке, потому что ошибка маловероятна.

                                                                0
                                                                Я в комментариях признал, что со словарём у меня ненадёжный код и даже внёс соответствующее примечание в публикацию.
                                                            0
                                                            Внешняя ссылка на экземпляр класса RipeType может появится только после выполнения конструктора, в каком бы потоке мы ни выполняли оператор new.

                                                            Нет, это не так. Другой поток может "увидеть" изменения в памяти не в том порядке в котором они вносились.

                                                              0
                                                              Поясните…

                                                              Ссылка на экземпляр объекта становится в первую очередь доступной в конструкторе, а потом уже вовне (если мы её не передали куда-то до завершения выполнения конструктора из самого конструктора). Исключение составляет случай создания объекта без вызова конструктора FormatterServices.GetUninitializedObject. Это насколько мне известно.
                                                                0
                                                                Вы все думаете в контексте одного потока. Но их у вас несколько.

                                                                А другой поток может увидеть как сначала был добавлен объект в хеш-таблицу, а потом уже у него был вызван конструктор.
                                                                  –1
                                                                  Пока не отработал конструктор объект никуда не может быть добавлен, поскольку на него ещё нигде нет внешних ссылок.
                                                                    0
                                                                    Третий раз повторяю: «не будет добавлен» и «ни один поток не увидит его добавленным» — две большие разницы.
                                                                      +3
                                                                      У вашего процессора несколько ядер, каждое со своим кэшом и своей личной копией кусочка памяти. Синхронизация кэшей происходит тоже кусочками и совсем не в том порядке, в котором вы что-то пишете в память. Соответственно, одно ядро может увидеть изменения в памяти не в том порядке, в котором они были сделаны другим ядром. Это если не учитывать ещё того, что компилятор может «немного» переписать ваш код и поменять порядок инструкций чтения/записи.
                                                                        0
                                                                        Чтобы воспроизвести такое нужны примеры намного похитрее, чем наш. :)
                                                                          0
                                                                          Уточнение: чтобы надежно воспроизвести. А вот случайно оно и на вашем примере однажды выплывет. Ночью на выходных, как уже тут писали в комментариях.
                                                                            0
                                                                            Все гораздо проще. Вот максимально простой аналог того, что происходит у вас — gist.github.com/retran/fa8c6b6671f0c91091a986a22d0f528b
                                                                              –1
                                                                              Пример интересный, но я не вижу аналогии с текущим случаем. Словарь внутри себя не дожидается выполнения потоков и не делает предположений, о значениях переменных. Грубо говоря, это просто массив, который может увеличиваться, сохраняя индексы элементов, с ненадёжным параллельным чтением (один поток может упустить изменения, только что внесённые другим).
                                                                    +2
                                                                    Ссылка на экземпляр объекта становится в первую очередь доступной в конструкторе, а потом уже вовне (если мы её не передали куда-то до завершения выполнения конструктора из самого конструктора).

                                                                    Насколько я понимаю, это не так. Сначала выделяется память для объекта, получается ссылка на неициализированный объект. Эта ссылка передаётся в вызов конструктора. Эта же ссылка используется в методе. И, если специально об этом не позаботиться, то гарантий, что ссылка не будет никуда сохранена до инициализации объекта нет!


                                                                    Подробнее можно посмотреть в CLR via C# Рихтера и серии статей про модель памяти C#:
                                                                    https://msdn.microsoft.com/magazine/jj863136


                                                                    Because the BoxedInt instance was incorrectly published (through a non-volatile field, _box), the thread that calls Print may observe a partially constructed object!
                                                                      0
                                                                      Спасибо, интересный пример, он очень напоминает передачу ссылки вовне из конструктора.
                                                                      class Tester
                                                                      {
                                                                        BoxedInt2 _box = null;
                                                                        public void Set() {
                                                                          _box = new BoxedInt2();
                                                                        }
                                                                        public void Print() {
                                                                          var b = _box;
                                                                          if (b != null) b.PrintValue();
                                                                        }
                                                                      }

                                                                      По идее, можно исправить так (если компилятор не соптимизирует)
                                                                      class Tester
                                                                      {
                                                                        BoxedInt2 _box = null;
                                                                        public void Set() {
                                                                          var tmp = new BoxedInt2();
                                                                          _box = tmp;
                                                                        }
                                                                        public void Print() {
                                                                          var b = _box;
                                                                          if (b != null) b.PrintValue();
                                                                        }
                                                                      }

                                                                      Поскольку вызов конструктора — блокирующая операция.
                                                                        0
                                                                        По идее, можно исправить так (если компилятор не соптимизирует)

                                                                        В том-то и дело, что нельзя так ничего исправить, потому что вы ничего не знаете про решения, которые будет принимать компилятор и JIT.

                                                                        0
                                                                        Но у меня не получилось воспроизвести ситуацию с недоинициализацией…

                                                                        using System;
                                                                        using System.Threading;
                                                                        using static System.Console;
                                                                        
                                                                        namespace Ace.Base.Console
                                                                        {
                                                                        	class MyClass
                                                                        	{
                                                                        		public static MyClass Instance;
                                                                        
                                                                        		private MyClass() => Thread.Sleep(5000);
                                                                        
                                                                        		public static void AsyncInit() => new Thread(() =>
                                                                        		{
                                                                        			WriteLine("Started");
                                                                        			Instance = new MyClass();
                                                                        		}).Start();
                                                                        	}
                                                                        
                                                                        	static class Program
                                                                        	{
                                                                        
                                                                        		static void Main(string[] args)
                                                                        		{
                                                                        			try
                                                                        			{
                                                                        				MyClass.AsyncInit();
                                                                        				Thread.Sleep(1000);
                                                                        				WriteLine("Ready");
                                                                        				WriteLine(MyClass.Instance?.ToString() ?? "<null>");
                                                                        			}
                                                                        			catch (Exception e)
                                                                        			{
                                                                        				WriteLine(e);
                                                                        				ReadKey(true);
                                                                        			}
                                                                        		}
                                                                        	}
                                                                        }
                                                                0

                                                                Первое правило многопоточности конкурентности: максимально использовать готовые компоненты.

                                                                  –3
                                                                  lock и Dictionary уже готовые, поэтому использую их по максимуму. :)
                                                                  За надёжность не ручаюсь, но выглядит работоспособно, мне было бы интересно словить ошибку в такой комбинации, если она возможна.

                                                                  Для большей уверенности, конечно, можно использовать ConcurrentDictionary, но его производительность я не измерял собственноручно.
                                                                    +1
                                                                    За надёжность не ручаюсь, но выглядит работоспособно

                                                                    В конкурентных сценариях много что "выглядит" работоспособно, вот только потом падает.


                                                                    мне было бы интересно словить ошибку в такой комбинации, если она возможна.

                                                                    Она, очевидно, возможна, потому что ничто в вашем коде не гарантирует вас от одновременного выполнения TryGetValue и [x] = y, а эти операции не взаимобезопасны.

                                                                  0
                                                                  Если в разных потоках создать два делегата от одного метода, то они будут равны, поэтому lock сработает корректно.

                                                                  Равны-то равны, но для lock требуется идентичность. А ее запросто может и не быть.

                                                                    +1

                                                                    В данном конкретном случае ее точно не будет, ибо замыкание. Так что можно считать, что лока нет.

                                                                      –1
                                                                      Её, как будто, гарантируют CLR и компилятор при инициализации статической переменной
                                                                          [CompilerGenerated]
                                                                          private sealed class <>c
                                                                          {
                                                                              public static readonly <>c <>9 = new <>c();
                                                                      

                                                                      (декомпиляция)

                                                                        +2
                                                                        Во-первых, нет. Компилятор ничего не гарантирует: сегодня он создает это поле, завтра — уже нет.

                                                                        Во-вторых, попробуйте декомпилировать свой код, а не какой-то тестовый. Если так не можете сообразить что замыкание с захваченными переменными не может быть сохранено в статическую переменную по построению.
                                                                          0
                                                                          Да, признаю, в этом предположении я оказался не прав. Нужно задавать более надёжный контекст для lock'а. Пример подправлю.
                                                                      +2
                                                                      Использование конструкций «довольно безопасное» или «крайне маловероятное» уже не комильфо. Данный подход если не сейчас, то в будущем обязательно приведет к малоприятным событиям. То что провели эксперимент, вы молодец, но смысла в нем не вижу, мало того, это даже опасно для неокрепших умов.
                                                                        –1
                                                                        Моя цель — поделиться идеями, подвергуть их критике и приблизиться истине. Вот с примененим лока на делегате уже нашли изъян, и я признаю, что оказался не прав. :)

                                                                        Думаю, это заставляет работать умы людей и более глубоко разбираться в вопросах.
                                                                    0
                                                                    Хм, неужели открыли type traits в C#. Видимо я пришёл с C++ и подобное использовал сразу, как занялся оптимизациями, даже не подозревал, что для мира C# это не так очевидно.
                                                                      –1
                                                                      Насколько понял из беглого ознакомления с type traits, идеи в основе схожие, но раньше мне не попадалось подобного рода оптимизаций на C#, разве что кэширование в переменную встречал (вместо многократного повторения вызова typeof(T).GetSomething()).

                                                                      Статический же TypeOf позволяет несколько обобщить подход, например, реализовать быстрый доступ к информации о типе из разных частей приложения, что довольно удобно, на мой взгляд.
                                                                      +2
                                                                      Вот еще заметил. Судя по бенчмарку, GetRipeType всегда медленнее чем простой GetType. Так зачем оно нужно?
                                                                        +2

                                                                        Более того, "типа-паттерн" c TypeOf<T> на Core и Mono медленнее, чем платформенное решение, а на CLR ошибка измерения больше, чем значение.


                                                                        (для получения собственно информации о типе)

                                                                          0
                                                                          Вот еще заметил. Судя по бенчмарку, GetRipeType всегда медленнее чем простой GetType. Так зачем оно нужно?

                                                                          Зависит от сценария использования. Если получается единожды закэшировать информацию о типе объекта, то потом быстрее её брать из RipeType, чем из Type

                                                                          static RipeType AnyRipeType = anyObject.GetRipeType();
                                                                          
                                                                          static void AnyPerformanceCriticalMethod()
                                                                          {
                                                                          	/* ... using of AnyRipeType ... */
                                                                          }

                                                                          В каждом конкретном случае нужно выбирать более оптимальное решение, чтобы достичь максимальной производительности, поскольку есть различия даже на разных CLR.
                                                                          +1
                                                                          Есть еще один распространенный и интересный case:
                                                                          typeof(SomeType) == someInstance.GetType()
                                                                          Такой код очень хорошо понимает компилятор и хорошо оптимизирует, фактически заменяет на TypeHandle == TypeHandle, что в итоге превращается в небольшое число процессорных инструкций.

                                                                          Предложенное Вами решение показывает себя хуже в этом распространенном сценарии.

                                                                          static private object stringObject = "";
                                                                          
                                                                          // ...
                                                                          
                                                                          [Benchmark] public bool typeof_string_Equals() => typeof(string) == stringObject.GetType();
                                                                          [Benchmark] public bool typeof_string_Is() => stringObject is string;
                                                                          [Benchmark] public bool TypeOf_string_Equals() => TypeOf<string>.Raw == stringObject.GetType();
                                                                          


                                                                          typeof_string_Equals: 1.533 ns
                                                                          typeof_string_Is: 1.759 ns
                                                                          TypeOf_string_Equals: 5.251 ns

                                                                          И, да, зря Вы спорите насчет потокобезопасности Dictionary. Он не просто потокоНЕбезопасен, он не всегда работает просто инвалидно с точки зрения данных — бывает он намертво вешает поток, который заходит за чтением, если другой поток зашел за модификацией.
                                                                            0
                                                                            Предложенное Вами решение показывает себя хуже в этом распространенном сценарии.

                                                                            а если в цикле вызывать много раз?
                                                                              0

                                                                              Вы на результаты бенчмарка смотрите.

                                                                                0
                                                                                Там «либа Акиньшина» используется, она и так гоняет все в цикле много раз.
                                                                                –1
                                                                                Да, каждый конкретный случай лучше рассматривать отдельно и выявлять наболее оптимальное решение, также стоит производить замеры на различных CLR, поскольку результаты иногда сильно плавают.
                                                                                +1

                                                                                Кстати, по сути поста.


                                                                                В этой статье представлены паттерны, позволяющие существенно повысить производительность множественных рефлексивных вызовов посредством техники виртуализации (кэширования) результатов.

                                                                                Memoization

                                                                                  0
                                                                                  Вот, кстати, было бы интересно сравнить производительность мемоизации на статической переменной и на замыканиях.
                                                                                    0
                                                                                    С удовольствием ознакомлюсь с результатами, если вы этим займётесь и поделитесь с остальными. :)
                                                                                  +1
                                                                                  Короче, если в TypeOf<> на core избавиться от статического конструктора и перенести логику инициализации в свойство или метод-геттер с проверкой на null, то результаты будут лучше где-то в 2-3 раза(может косяк в статических конструкторах, а может так и надо — не знаю). А если избавиться от проверки на null, то у меня получилось значения примерно как на clr. Но я так понимаю, что это нужно точно быть уверенным, что поле будет инициализировано до момента использования.
                                                                                    0

                                                                                    … и что вы будете делать с потокобезопасностью?

                                                                                      0
                                                                                      ничего. я поставил себе задачу выяснить почему на core медленее работает. И я выяснил. а если решать задачу о применении, то тут сперва нужно подумать, что делать с самим решением… Поле ведь не является readonly. и само по себе архитектурно плохо спроектировано. Ведь без инициализации в конструкторе и readonly нельзя быть уверенным на все сто, что не будет null. А если вставлять проверку, то опять же — потеря производительности и лучше использовать typeof().