虽然大部分情况下这不会成为性能瓶颈,但是无论性能还是可读性,TryGetValue都比ContainsKey+dic[key]要更好,最近在项目里发现了一堆ContainsKey+dic[key]。但2026年了,这应该成为肌肉记忆了。于是写了一篇性能测试加强一下记忆。
Unity测试代码
public class DictionaryPerformanceTests
{
private const int DictionarySize = 1000;
private const int IterationsPerMeasurement = 1000;
private Dictionary<int, int> _dictionary;
[SetUp]
public void Setup()
{
_dictionary = new Dictionary<int, int>(DictionarySize);
for (var i = 0; i < DictionarySize; i++)
{
_dictionary.Add(i, i);
}
}
// 用成员变量做"黑洞",防止编译器/JIT 把读取优化掉导致测量失真
private int _blackhole;
[Test, Performance]
public void Dictionary_TryGetValue()
{
Measure.Method(() =>
{
for (var i = 0; i < DictionarySize; i++)
{
if (_dictionary.TryGetValue(i, out var value))
_blackhole = value;
}
})
.SampleGroup("TryGetValue")
.WarmupCount(10)
.MeasurementCount(100)
.IterationsPerMeasurement(IterationsPerMeasurement)
.GC()
.Run();
}
[Test, Performance]
public void Dictionary_ContainsKey_GetItem()
{
Measure.Method(() =>
{
for (var i = 0; i < DictionarySize; i++)
{
if (_dictionary.ContainsKey(i))
_blackhole = _dictionary[i];
}
})
.SampleGroup("ContainsKey + this[key]")
.WarmupCount(10)
.MeasurementCount(100)
.IterationsPerMeasurement(IterationsPerMeasurement)
.GC()
.Run();
}
}
Unity测结果
TryGetValue in Milliseconds
Min: 45.14 ms
Median: 46.94 ms
Max: 63.18 ms
Avg: 47.64 ms
StdDev: 2.64 ms
SampleCount: 100
Sum: 4763.97 ms
ContainsKey + this[key] in Milliseconds
Min: 84.95 ms
Median: 86.77 ms
Max: 97.57 ms
Avg: 87.75 ms
StdDev: 2.85 ms
SampleCount: 100
Sum: 8774.52 ms
.Net测试代码
public class TryGetValueContext
{
private Dictionary<int, int> _dictionary = null!;
private readonly Consumer _consumer = new Consumer(); // 防优化"黑洞"
[Params(1000)]
public int Size;
[GlobalSetup]
public void Setup()
{
_dictionary = new Dictionary<int, int>(Size);
for (var i = 0; i < Size; i++)
_dictionary.Add(i, i);
}
// 方式1:TryGetValue —— 一次哈希查找
[Benchmark(Baseline = true)]
public void TryGetValue_Hit()
{
var consumer = _consumer;
var dict = _dictionary;
for (var i = 0; i < Size; i++)
{
if (dict.TryGetValue(i, out var value))
consumer.Consume(value);
}
}
// 方式2:ContainsKey + 索引器 —— 两次哈希查找
[Benchmark]
public void ContainsKey_Indexer_Hit()
{
var consumer = _consumer;
var dict = _dictionary;
for (var i = 0; i < Size; i++)
{
if (dict.ContainsKey(i))
{
var value = dict[i];
consumer.Consume(value);
}
}
}
// 方式3:直接索引器(key 必然存在,不判断)
[Benchmark]
public void Indexer_Direct()
{
var consumer = _consumer;
var dict = _dictionary;
for (var i = 0; i < Size; i++)
consumer.Consume(dict[i]);
}
// 方式4:TryGetValue miss(key 不存在)—— 对照组
[Benchmark]
public void TryGetValue_Miss()
{
var consumer = _consumer;
var dict = _dictionary;
for (var i = 0; i < Size; i++)
{
if (dict.TryGetValue(i + Size, out var value)) // 全部 miss
consumer.Consume(value);
}
}
// 方式5:ContainsKey + Indexer miss —— 说明 miss 时两者差别不大
[Benchmark]
public void ContainsKey_Indexer_Miss()
{
var dict = _dictionary;
var count = 0;
for (var i = 0; i < Size; i++)
{
if (dict.ContainsKey(i + Size))
count += dict[i + Size];
}
_consumer.Consume(count);
}
}
.NET测试结果
| Method | Size | Mean | Error | StdDev | Ratio | RatioSD |
|---|---|---|---|---|---|---|
| TryGetValue_Hit | 1000 | 4.350 us | 0.0519 us | 0.0486 us | 1.00 | 0.00 |
| ContainsKey_Indexer_Hit | 1000 | 8.103 us | 0.0715 us | 0.0669 us | 1.86 | 0.03 |
| Indexer_Direct | 1000 | 3.838 us | 0.0283 us | 0.0264 us | 0.88 | 0.01 |
| TryGetValue_Miss | 1000 | 3.347 us | 0.0267 us | 0.0250 us | 0.77 | 0.01 |
| ContainsKey_Indexer_Miss | 1000 | 3.327 us | 0.0203 us | 0.0190 us | 0.76 | 0.01 |
结论
和理论一样, 无论是Unity还是.Net都几乎是翻倍的差距
594



被折叠的 条评论
为什么被折叠?



