我调查了性能下降的原因,并将其追踪到缓慢的哈希集上。
我的结构体具有可为空的值,用作主键。例如:
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);
}
结果:
方法 | 中位数 | 缩放比例 ---------------- |--------------- |---------- 长整型 | 22.8682 微秒 | 0.42 可空长整型 | 39.0337 微秒 | 0.62 包装类 | 62.8877 微秒 | 1.00 可空包装类 | 231,993.7278 微秒 | 3,540.34
使用一个带有 Nullable<long>
的结构体与使用一个带有 long
的结构体相比慢了3540倍!
在我的情况下,这使得800毫秒和小于1毫秒之间的差别。
以下是从BenchmarkDotNet中提取的环境信息:
操作系统=Microsoft Windows NT 6.1.7601 Service Pack 1
处理器=Intel(R) Core(TM) i7-5600U CPU 2.60GHz,ProcessorCount=4
频率=2536269 滴答,分辨率=394.2799 纳秒,计时器=TSC
CLR=MS.NET 4.0.30319.42000,架构=64-bit RELEASE [RyuJIT]
GC=Concurrent Workstation
JitModules=clrjit-v4.6.1076.0
为什么性能这么差呢?
GetHashCode
和Equals
方法?默认实现会使用反射。为了避免装箱,你还应该实现IEquatable<NullableLongWrapper>
接口。 - LeeGetHashCode
和Equals
。不过这是一个很好的解决方法,我还没有尝试过。 - Kobilong?
已经是一个“可空长整型包装器”(其实际类型为Nullable<long>
),所以没有必要为它创建一个结构体。 - BlueRaja - Danny Pflughoeftlong?
。它类似于外部连接的结果,其中左边或右边可能是null
。 - Kobi