Я исследовал ухудшение производительности и отследил его до медленных HashSets.
У меня есть структуры с нулевыми значениями, которые используются в качестве первичного ключа. Например:
public struct NullableLongWrapper
{
private readonly long? _value;
public NullableLongWrapper(long? value)
{
_value = value;
}
}
Я заметил, что создание HashSet<NullableLongWrapper>
исключительно медленное.
Вот пример использования BenchmarkDotNet: (Install-Package BenchmarkDotNet
)
using System.Collections.Generic;
using System.Linq;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Configs;
using BenchmarkDotNet.Jobs;
using BenchmarkDotNet.Running;
public class Program
{
static void Main()
{
BenchmarkRunner.Run<HashSets>();
}
}
public class Config : ManualConfig
{
public Config()
{
Add(Job.Dry.WithWarmupCount(1).WithLaunchCount(3).WithTargetCount(20));
}
}
public struct NullableLongWrapper
{
private readonly long? _value;
public NullableLongWrapper(long? value)
{
_value = value;
}
public long? Value => _value;
}
public struct LongWrapper
{
private readonly long _value;
public LongWrapper(long value)
{
_value = value;
}
public long Value => _value;
}
[Config(typeof (Config))]
public class HashSets
{
private const int ListSize = 1000;
private readonly List<long?> _nullables;
private readonly List<long> _longs;
private readonly List<NullableLongWrapper> _nullableWrappers;
private readonly List<LongWrapper> _wrappers;
public HashSets()
{
_nullables = Enumerable.Range(1, ListSize).Select(i => (long?) i).ToList();
_longs = Enumerable.Range(1, ListSize).Select(i => (long) i).ToList();
_nullableWrappers = Enumerable.Range(1, ListSize).Select(i => new NullableLongWrapper(i)).ToList();
_wrappers = Enumerable.Range(1, ListSize).Select(i => new LongWrapper(i)).ToList();
}
[Benchmark]
public void Longs() => new HashSet<long>(_longs);
[Benchmark]
public void NullableLongs() => new HashSet<long?>(_nullables);
[Benchmark(Baseline = true)]
public void Wrappers() => new HashSet<LongWrapper>(_wrappers);
[Benchmark]
public void NullableWrappers() => new HashSet<NullableLongWrapper>(_nullableWrappers);
}
Результат:
Method | Median | Scaled ----------------- |---------------- |--------- Longs | 22.8682 us | 0.42 NullableLongs | 39.0337 us | 0.62 Wrappers | 62.8877 us | 1.00 NullableWrappers | 231,993.7278 us | 3,540.34
Использование структуры с Nullable<long>
по сравнению с структурой с long
в 3540 раз медленнее!
В моем случае это делало разницу между 800 мс и < 1 мс.
Вот информация об окружающей среде от BenchmarkDotNet:
ОС = Microsoft Windows NT 6.1.7601 Пакет обновления 1
Процессор = Intel (R) Core (TM) i7-5600U CPU 2,60 ГГц, ProcessorCount = 4
Частота = 2536269 тиков, разрешение = 394,2799 нс, таймер = TSC
CLR = MS.NET 4.0.30319.42000, Arch = 64-разрядный RELEASE [RyuJIT]
GC = параллельная рабочая станция
JitModules = clrjit-v4.6.1076.0
Какова причина, по которой это бедность?