Почему HashSets структур с обнуляемыми значениями невероятно медленны?

Я исследовал снижение производительности и отследил его до медленных 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 ЦП 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

В чем причина плохой производительности?

Ответы на вопрос(2)

Ваш ответ на вопрос