Страницы

Поиск по вопросам

суббота, 14 декабря 2019 г.

Сравнение алгоритмов проверки переменной на значение по умолчанию

#c_sharp #net #рефлексия


Update: Самым быстрым оказался алгоритм V7

Продолжение предыдущего вопроса.

Есть несколько вариантов реализации функции для проверки переменной на значение по
умолчанию. Из комментариев к предыдущему вопросу я понял лишь то, что мне еще очень
много предстоит выучить в C#. Помогите пожалуйста найти самый производительный и надежный
вариант реализации функции.

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

Некоторые варианты реализации функции:

V1:

bool IsDefault(T o)
{
    if (o == null) // => ссылочный тип или nullable
        return true;
    if (Nullable.GetUnderlyingType(typeof(T)) != null) // nullable, не null
        return false;
    var type = o.GetType();
    //для .net core type.GetTypeInfo().IsClass
    if (type.IsClass) 
        return false;
    else       // => тип-значение, есть конструктор по умолчанию
        return Activator.CreateInstance(type).Equals(o);
}


V3 (улучшенный)

bool isDefault(T o) 
{ 
    return (o==null)?true: // считаем что null default
      o.GetType().IsValueType && !typeof(T).IsGenericType ? 
      o.Equals(Activator.CreateInstance(o.GetType())): // default типа который внутри
nullable
      o.Equals(default(T)); // настоящий default
}


V5 смешать V1 и большое кол-во перегрузок под все типы:

public class DefaultChecker
{
    bool IsDefault(byte value) => value == 0;
    bool IsDefault(byte? value) => value == null;
    bool IsDefault(sbyte value) => value == 0;
    bool IsDefault(sbyte? value) => value == null;
    bool IsDefault(int value) => value == 0;
    bool IsDefault(int? value) => value == null;
    bool IsDefault(uint value) => value == 0;
    bool IsDefault(uint? value) => value == null;
    bool IsDefault(short value) => value == 0;
    bool IsDefault(short? value) => value == null;
    bool IsDefault(ushort value) => value == 0;
    bool IsDefault(ushort? value) => value == null;
    bool IsDefault(long value) => value == 0;
    bool IsDefault(long? value) => value == null;
    bool IsDefault(ulong value) => value == 0;
    bool IsDefault(ulong? value) => value == null;
    bool IsDefault(float value) => value == 0.0F;
    bool IsDefault(float? value) => value == null;
    bool IsDefault(double value) => value == 0.0D;
    bool IsDefault(double? value) => value == null;
    bool IsDefault(char value) => value == '\0';
    bool IsDefault(char? value) => value == null;
    bool IsDefault(bool value) => !value;
    bool IsDefault(bool? value) => value == null;
    bool IsDefault(string value) => value == null;
    bool IsDefault(decimal value) => value == 0.0M;
    bool IsDefault(decimal? value) => value == null;

    public bool IsDefault(T value)
    {
        if (value == null) // => ссылочный тип или nullable
            return true;
        if (Nullable.GetUnderlyingType(typeof(T)) != null) // nullable, не null
            return false;
        var type = value.GetType();
        //для .net core type.GetTypeInfo().IsClass
        if (type.IsClass)
            return false;
        else       // => тип-значение, есть конструктор по умолчанию
            return Activator.CreateInstance(type).Equals(value);
    }
}


V6:

class RequireStruct where T : struct { }
class RequireClass where T : class { }

static bool IsDefault(T o, RequireClass ignore = null) where T : class
{
    if (o == null)
        return true;
    if (!(o is ValueType)) // не упакованная ли это структура?
        return false;      // нет - выходим
    return Activator.CreateInstance(o.GetType()).Equals(o); // медленный путь
}

static bool IsDefault(T? o) where T : struct =>
    o == null;

static bool IsDefault(T o, RequireStruct ignore = null) where T : struct =>
   default(T).Equals(o); // default(T) не требует рефлексии


V7 смешать V6 и большое кол-во перегрузок под все типы:

bool IsDefault(byte value) => value == 0;
bool IsDefault(byte? value) => value == null;
bool IsDefault(sbyte value) => value == 0;
bool IsDefault(sbyte? value) => value == null;
bool IsDefault(int value) => value == 0;
bool IsDefault(int? value) => value == null;
bool IsDefault(uint value) => value == 0;
bool IsDefault(uint? value) => value == null;
bool IsDefault(short value) => value == 0;
bool IsDefault(short? value) => value == null;
bool IsDefault(ushort value) => value == 0;
bool IsDefault(ushort? value) => value == null;
bool IsDefault(long value) => value == 0;
bool IsDefault(long? value) => value == null;
bool IsDefault(ulong value) => value == 0;
bool IsDefault(ulong? value) => value == null;
bool IsDefault(float value) => value == 0.0F;
bool IsDefault(float? value) => value == null;
bool IsDefault(double value) => value == 0.0D;
bool IsDefault(double? value) => value == null;
bool IsDefault(char value) => value == '\0';
bool IsDefault(char? value) => value == null;
bool IsDefault(bool value) => !value;
bool IsDefault(bool? value) => value == null;
bool IsDefault(string value) => value == null;
bool IsDefault(decimal value) => value == 0.0M;
bool IsDefault(decimal? value) => value == null;
bool IsDefault(T o, RequireClass ignore = null) where T : class
{
    if (o == null)
        return true;
    if (!(o is ValueType))
        return false;
    return Activator.CreateInstance(o.GetType()).Equals(o);
}
bool IsDefault(T? o) where T : struct =>
    o == null;
bool IsDefault(T o, RequireStruct ignore = null) where T : struct =>
    default(T).Equals(o);


---Реализации, которые не проходят тест---

V2:

public object GetDefaultValue(Type target)
{
    Expression> e = Expression.Lambda>(
        Expression.Convert(
            Expression.Default(target), typeof(object)));
    return e.Compile()();
}

public bool IsDefault(object o)
{
    if(o == null)
        throw new ArgumentNullException(nameof(o));

    return o.Equals(GetDefaultValue(o.GetType()));
}


V3:

bool isDefault(T o)
{ 
    return (o==null)?true:
        o.GetType().IsValueType ?  
        Activator.CreateInstance(o.GetType()).Equals(o) : 
        o.Equals(default(T)); 
}


V4:

//isDefault((object)0) этот вариант даст false
bool isDefault(T o) 
{ 
    return (o==null)?(default(T)==null):o.Equals(default(T)); 
}

    


Ответы

Ответ 1



Для начала, нужно составить юнит-тесты на желаемую функцию. Например, такие: Debug.Assert(IsDefault(default(string))); Debug.Assert(IsDefault((object)default(string))); Debug.Assert(!IsDefault(string.Empty)); Debug.Assert(!IsDefault((object)string.Empty)); Debug.Assert(IsDefault(default(int))); Debug.Assert(!IsDefault(1)); Debug.Assert(IsDefault(default(int?))); Debug.Assert(!IsDefault((int?)0)); Debug.Assert(IsDefault((object)0)); Debug.Assert(!IsDefault((object)1)); Пробуем различные имплементации: V1 проходит. V2 бросает ArgumentNullException вместо того, чтобы вернуть false, на Debug.Assert(IsDefault(default(string)));. V3 не проходит Debug.Assert(!IsDefault((int?)0)); проходит. V4 не проходит Debug.Assert(IsDefault((object)0)). V5 проходит. V6 проходит. V7 проходит. Окей, на текущий момент тесты проходят V1, V3, V5, V6 и V7. Тестировалось при помощи BenchmarkDotNet, результаты ниже. Вот результаты: BenchmarkDotNet=v0.10.9, OS=Windows 10 Redstone 2 (10.0.15063) Processor=Intel Core i7-6700 CPU 3.40GHz (Skylake), ProcessorCount=8 Frequency=3328123 Hz, Resolution=300.4697 ns, Timer=TSC [Host] : .NET Framework 4.7 (CLR 4.0.30319.42000), 64bit RyuJIT-v4.7.2102.0 DefaultJob : .NET Framework 4.7 (CLR 4.0.30319.42000), 64bit RyuJIT-v4.7.2102.0 Method | Mean | Error | StdDev | ------- |-----------:|---------:|---------:| TestV1 | 1,793.9 ns | 4.960 ns | 4.639 ns | TestV3 | 1,260.1 ns | 6.523 ns | 5.782 ns | TestV5 | 1,195.5 ns | 9.269 ns | 7.740 ns | TestV6 | 481.8 ns | 2.526 ns | 2.363 ns | TestV7 | 445.3 ns | 1.847 ns | 1.728 ns | Обратите внимание, что код проверки пробегает в худшем случае за менее двух, а в лучшем — за половину микросекунды (то есть, одной миллионной части секунды). Поэтому искать выигрыш в любой из реализаций нет особенного смысла: все реализации пробегают очень быстро. Единственный случай, при котором имеет смысл задуматься об оптимизации — это если такая вот операция вызывается десятки тысяч раз. (Но в этом случае, возможно, имеет смысл пересмотреть дизайн программы.) Если кому интересно, вот код сравнения (длинный и скучный): using System; using System.Collections.Generic; using System.Diagnostics; using BenchmarkDotNet.Attributes; using BenchmarkDotNet.Running; namespace Т { public class Program { public static void Main(string[] args) { var summary = BenchmarkRunner.Run(); } } public class Benchmarks { bool IsDefaultV1(T o) { if (o == null) // => ссылочный тип или nullable return true; if (Nullable.GetUnderlyingType(typeof(T)) != null) // nullable, не null return false; var type = o.GetType(); if (type.IsClass) return false; else // => тип-значение, есть конструктор по умолчанию return Activator.CreateInstance(type).Equals(o); } bool IsDefaultV3(T o) { return (o == null) ? true : // считаем что null default o.GetType().IsValueType && !typeof(T).IsGenericType ? o.Equals(Activator.CreateInstance(o.GetType())) : // default типа который внутри nullable o.Equals(default(T)); // настоящий default } public bool IsDefaultV5(byte value) => value == 0; public bool IsDefaultV5(byte? value) => value == null; public bool IsDefaultV5(sbyte value) => value == 0; public bool IsDefaultV5(sbyte? value) => value == null; public bool IsDefaultV5(int value) => value == 0; public bool IsDefaultV5(int? value) => value == null; public bool IsDefaultV5(uint value) => value == 0; public bool IsDefaultV5(uint? value) => value == null; public bool IsDefaultV5(short value) => value == 0; public bool IsDefaultV5(short? value) => value == null; public bool IsDefaultV5(ushort value) => value == 0; public bool IsDefaultV5(ushort? value) => value == null; public bool IsDefaultV5(long value) => value == 0; public bool IsDefaultV5(long? value) => value == null; public bool IsDefaultV5(ulong value) => value == 0; public bool IsDefaultV5(ulong? value) => value == null; public bool IsDefaultV5(float value) => value == 0.0F; public bool IsDefaultV5(float? value) => value == null; public bool IsDefaultV5(double value) => value == 0.0D; public bool IsDefaultV5(double? value) => value == null; public bool IsDefaultV5(char value) => value == '\0'; public bool IsDefaultV5(char? value) => value == null; public bool IsDefaultV5(bool value) => !value; public bool IsDefaultV5(bool? value) => value == null; public bool IsDefaultV5(string value) => value == null; public bool IsDefaultV5(decimal value) => value == 0.0M; public bool IsDefaultV5(decimal? value) => value == null; public bool IsDefaultV5(T value) { if (value == null) // => ссылочный тип или nullable return true; if (Nullable.GetUnderlyingType(typeof(T)) != null) // nullable, не null return false; var type = value.GetType(); //для .net core type.GetTypeInfo().IsClass if (type.IsClass) return false; else // => тип-значение, есть конструктор по умолчанию return Activator.CreateInstance(type).Equals(value); } class RequireStruct where T : struct { } class RequireClass where T : class { } static bool IsDefaultV6(T o, RequireClass ignore = null) where T : class { if (o == null) return true; if (!(o is ValueType)) return false; return Activator.CreateInstance(o.GetType()).Equals(o); } static bool IsDefaultV6(T? o) where T : struct => o == null; static bool IsDefaultV6(T o, RequireStruct ignore = null) where T : struct => default(T).Equals(o); static bool IsDefaultV7(byte value) => value == 0; static bool IsDefaultV7(byte? value) => value == null; static bool IsDefaultV7(sbyte value) => value == 0; static bool IsDefaultV7(sbyte? value) => value == null; static bool IsDefaultV7(int value) => value == 0; static bool IsDefaultV7(int? value) => value == null; static bool IsDefaultV7(uint value) => value == 0; static bool IsDefaultV7(uint? value) => value == null; static bool IsDefaultV7(short value) => value == 0; static bool IsDefaultV7(short? value) => value == null; static bool IsDefaultV7(ushort value) => value == 0; static bool IsDefaultV7(ushort? value) => value == null; static bool IsDefaultV7(long value) => value == 0; static bool IsDefaultV7(long? value) => value == null; static bool IsDefaultV7(ulong value) => value == 0; static bool IsDefaultV7(ulong? value) => value == null; static bool IsDefaultV7(float value) => value == 0.0F; static bool IsDefaultV7(float? value) => value == null; static bool IsDefaultV7(double value) => value == 0.0D; static bool IsDefaultV7(double? value) => value == null; static bool IsDefaultV7(char value) => value == '\0'; static bool IsDefaultV7(char? value) => value == null; static bool IsDefaultV7(bool value) => !value; static bool IsDefaultV7(bool? value) => value == null; static bool IsDefaultV7(string value) => value == null; static bool IsDefaultV7(decimal value) => value == 0.0M; static bool IsDefaultV7(decimal? value) => value == null; static bool IsDefaultV7(T o, RequireClass ignore = null) where T : class { if (o == null) return true; if (!(o is ValueType)) return false; return Activator.CreateInstance(o.GetType()).Equals(o); } static bool IsDefaultV7(T? o) where T : struct => o == null; static bool IsDefaultV7(T o, RequireStruct ignore = null) where T : struct => default(T).Equals(o); struct Test { int x; public Test(int x) { this.x = x; } } List l1 = default; List l2 = new List(); string s1 = default; string s2 = string.Empty; int i1 = default; int i2 = 1; int? ni1 = default; int? ni2 = 0; object bi1 = 0; object bi2 = 1; Test t1 = default; Test t2 = new Test(1); object tb1 = default(Test); object tb2 = new Test(1); ValueType tc1 = default(Test); ValueType tc2 = new Test(1); [Benchmark] public int TestV1() { return (IsDefaultV1(l1) ? 1 : 0) + (!IsDefaultV1(l2) ? 1 : 0) + (IsDefaultV1(s1) ? 1 : 0) + (!IsDefaultV1(s2) ? 1 : 0) + (IsDefaultV1(i1) ? 1 : 0) + (!IsDefaultV1(i2) ? 1 : 0) + (IsDefaultV1(ni1) ? 1 : 0) + (!IsDefaultV1(ni2) ? 1 : 0) + (IsDefaultV1(bi1) ? 1 : 0) + (!IsDefaultV1(bi2) ? 1 : 0) + (IsDefaultV1(t1) ? 1 : 0) + (!IsDefaultV1(t2) ? 1 : 0) + (IsDefaultV1(tb1) ? 1 : 0) + (!IsDefaultV1(tb2) ? 1 : 0) + (IsDefaultV1(tc1) ? 1 : 0) + (!IsDefaultV1(tc2) ? 1 : 0); } [Benchmark] public int TestV3() { return (IsDefaultV3(l1) ? 1 : 0) + (!IsDefaultV3(l2) ? 1 : 0) + (IsDefaultV3(s1) ? 1 : 0) + (!IsDefaultV3(s2) ? 1 : 0) + (IsDefaultV3(i1) ? 1 : 0) + (!IsDefaultV3(i2) ? 1 : 0) + (IsDefaultV3(ni1) ? 1 : 0) + (!IsDefaultV3(ni2) ? 1 : 0) + (IsDefaultV3(bi1) ? 1 : 0) + (!IsDefaultV3(bi2) ? 1 : 0) + (IsDefaultV3(t1) ? 1 : 0) + (!IsDefaultV3(t2) ? 1 : 0) + (IsDefaultV3(tb1) ? 1 : 0) + (!IsDefaultV3(tb2) ? 1 : 0) + (IsDefaultV3(tc1) ? 1 : 0) + (!IsDefaultV3(tc2) ? 1 : 0); } [Benchmark] public int TestV5() { return (IsDefaultV5(l1) ? 1 : 0) + (!IsDefaultV5(l2) ? 1 : 0) + (IsDefaultV5(s1) ? 1 : 0) + (!IsDefaultV5(s2) ? 1 : 0) + (IsDefaultV5(i1) ? 1 : 0) + (!IsDefaultV5(i2) ? 1 : 0) + (IsDefaultV5(ni1) ? 1 : 0) + (!IsDefaultV5(ni2) ? 1 : 0) + (IsDefaultV5(bi1) ? 1 : 0) + (!IsDefaultV5(bi2) ? 1 : 0) + (IsDefaultV5(t1) ? 1 : 0) + (!IsDefaultV5(t2) ? 1 : 0) + (IsDefaultV5(tb1) ? 1 : 0) + (!IsDefaultV5(tb2) ? 1 : 0) + (IsDefaultV5(tc1) ? 1 : 0) + (!IsDefaultV5(tc2) ? 1 : 0); } [Benchmark] public int TestV6() { return (IsDefaultV6(l1) ? 1 : 0) + (!IsDefaultV6(l2) ? 1 : 0) + (IsDefaultV6(s1) ? 1 : 0) + (!IsDefaultV6(s2) ? 1 : 0) + (IsDefaultV6(i1) ? 1 : 0) + (!IsDefaultV6(i2) ? 1 : 0) + (IsDefaultV6(ni1) ? 1 : 0) + (!IsDefaultV6(ni2) ? 1 : 0) + (IsDefaultV6(bi1) ? 1 : 0) + (!IsDefaultV6(bi2) ? 1 : 0) + (IsDefaultV6(t1) ? 1 : 0) + (!IsDefaultV6(t2) ? 1 : 0) + (IsDefaultV6(tb1) ? 1 : 0) + (!IsDefaultV6(tb2) ? 1 : 0) + (IsDefaultV6(tc1) ? 1 : 0) + (!IsDefaultV6(tc2) ? 1 : 0); } [Benchmark] public int TestV7() { return (IsDefaultV7(l1) ? 1 : 0) + (!IsDefaultV7(l2) ? 1 : 0) + (IsDefaultV7(s1) ? 1 : 0) + (!IsDefaultV7(s2) ? 1 : 0) + (IsDefaultV7(i1) ? 1 : 0) + (!IsDefaultV7(i2) ? 1 : 0) + (IsDefaultV7(ni1) ? 1 : 0) + (!IsDefaultV7(ni2) ? 1 : 0) + (IsDefaultV7(bi1) ? 1 : 0) + (!IsDefaultV7(bi2) ? 1 : 0) + (IsDefaultV7(t1) ? 1 : 0) + (!IsDefaultV7(t2) ? 1 : 0) + (IsDefaultV7(tb1) ? 1 : 0) + (!IsDefaultV7(tb2) ? 1 : 0) + (IsDefaultV7(tc1) ? 1 : 0) + (!IsDefaultV7(tc2) ? 1 : 0); } public void CheckCorrectness() { var testMethods = this.GetType() .GetMethods() .Where(m => m.Name.StartsWith("TestV")) .Select(m => (name: m.Name, func: (Func)m.CreateDelegate(typeof(Func), this))) .ToList(); foreach ((var name, var func) in testMethods) { Console.Write($"Testing {name}... "); var ok = func() == 16; Console.WriteLine(ok ? "success" : "FAILED"); } } } }

Комментариев нет:

Отправить комментарий