Страницы

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

вторник, 23 октября 2018 г.

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

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)); }


Ответ

Для начала, нужно составить юнит-тесты на желаемую функцию. Например, такие:
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"); } } } }

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

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