假设以下代码:
if (myDictionary.ContainsKey(aKey))
myDictionary[aKey] = aValue;
else
myDictionary.Add(aKey, aValue);
此代码两次访问字典,一次用于确定
aKey
是否存在,另一次用于更新(如果存在)或添加(如果不存在)。我猜想这种方法只执行几次,这种方法的性能是“可以接受的”。但是,在我的应用程序中,类似的代码大约执行了500K次。我分析了我的代码,它显示了此部分花费了80%的CPU时间(请参见下图),因此可以进行改进。注意,字典是
lambdas
。第一个解决方法很简单:
myDictionary[aKey] = aValue;
如果存在
aKey
,则将其值替换为aValue
;如果不存在,则将KeyValuePair
作为键并将aKey
作为值的aValue
添加到myDictionary
中。但是,此方法有两个缺点:首先,您不知道
aKey
是否存在,这会阻止您使用其他逻辑。例如,您不能基于此替代方法重写以下代码:int addCounter = 0, updateCounter = 0;
if (myDictionary.ContainsKey(aKey))
{
myDictionary[aKey] = aValue;
addCounter++;
}
else
{
myDictionary.Add(aKey, aValue);
updateCounter++;
}
其次,更新不能是旧值的函数。例如,您不能执行类似于以下的逻辑:
if (myDictionary.ContainsKey(aKey))
myDictionary[aKey] = (myDictionary[aKey] * 2) + aValue;
else
myDictionary.Add(aKey, aValue);
第二种解决方法是使用
ConcurrentDictionary
。显然,使用delegates
我们可以解决上述第二个问题;但是,我仍然不清楚如何解决第一个问题。提醒一下,我担心的是加快速度。鉴于只有一个线程正在使用此过程,所以我认为仅值得使用
ConcurrentDictionary
的一个线程不会对并发(带锁)的惩罚。我错过了一点吗?有谁有更好的建议?
最佳答案
如果您真的想要像AddOrUpdate
中那样的ConcurrentDictionary
方法,但不影响使用jsont_code方法的性能,则您必须自己实现此类Dictionary。
好消息是,由于CoreCLR是开源的,因此您可以从CoreCLR repository获取实际的.Net词典源,并应用您自己的修改。似乎并不难,请看那里的Insert
私有(private)方法。
一种可能的实现方式是(未经测试):
public void AddOrUpdate(TKey key, Func<TKey, TValue> adder, Func<TKey, TValue, TValue> updater) {
if( key == null ) {
ThrowHelper.ThrowArgumentNullException(ExceptionArgument.key);
}
if (buckets == null) Initialize(0);
int hashCode = comparer.GetHashCode(key) & 0x7FFFFFFF;
int targetBucket = hashCode % buckets.Length;
for (int i = buckets[targetBucket]; i >= 0; i = entries[i].next) {
if (entries[i].hashCode == hashCode && comparer.Equals(entries[i].key, key)) {
entries[i].value = updater(key, entries[i].value);
version++;
return;
}
}
int index;
if (freeCount > 0) {
index = freeList;
freeList = entries[index].next;
freeCount--;
}
else {
if (count == entries.Length)
{
Resize();
targetBucket = hashCode % buckets.Length;
}
index = count;
count++;
}
entries[index].hashCode = hashCode;
entries[index].next = buckets[targetBucket];
entries[index].key = key;
entries[index].value = adder(key);
buckets[targetBucket] = index;
version++;
}