用于C#字典的Atomic AddOrUpdate

Ham*_*med 11 c# dictionary atomic

假设以下代码:

if (myDictionary.ContainsKey(aKey))
    myDictionary[aKey] = aValue;
else
    myDictionary.Add(aKey, aValue);
Run Code Online (Sandbox Code Playgroud)

此代码访问字典两次,一次用于确定是否aKey存在,另一次用于更新(如果存在)或添加(如果不存在).我想这个代码只执行几次时,这种方法的性能是"可接受的".但是,在我的应用程序中,类似的代码执行大约500K次.我描述了我的代码,它显示了80%的CPU时间花在了这一部分上(见下图),因此这有助于改进.

请注意,字典是lambdas.

第一个解决方法很简单:

myDictionary[aKey] = aValue;
Run Code Online (Sandbox Code Playgroud)

如果aKey存在,则将其值替换为aValue; 如果不存在,KeyValuePairaKey关键和aValue作为价值被添加到myDictionary.但是,这种方法有两个缺点:

首先,您不知道是否aKey存在会阻止您使用其他逻辑.例如,您无法根据此变通方法重写以下代码:

int addCounter = 0, updateCounter = 0;
if (myDictionary.ContainsKey(aKey))
{
    myDictionary[aKey] = aValue;
    addCounter++;
}
else
{
    myDictionary.Add(aKey, aValue);
    updateCounter++;
}
Run Code Online (Sandbox Code Playgroud)

其次,更新不能是旧值的函数.例如,你不能做一个类似于以下的逻辑:

if (myDictionary.ContainsKey(aKey))    
    myDictionary[aKey] = (myDictionary[aKey] * 2) + aValue;    
else    
    myDictionary.Add(aKey, aValue);
Run Code Online (Sandbox Code Playgroud)

第二种解决方法是使用ConcurrentDictionary.很明显,使用 delegates我们可以解决上述第二个问题; 但是,我还不清楚我们如何解决第一个问题.

提醒一下,我担心的是加速.鉴于只有一个线程使用此过程,我不认为只有一个线程值得使用并发(带锁)的惩罚ConcurrentDictionary.

我错过了一点吗?有没有人有更好的建议?

gho*_*ord 3

如果您确实想要AddOrUpdate类似 in 的方法,ConcurrentDictionary但又不影响使用该方法的性能,那么您必须自己实现这样的字典。

好消息是,由于 CoreCLR 是开源的,您可以从CoreCLR 存储库获取实际的 .Net Dictionary 源代码并应用您自己的修改。看来不会那么难,看一下Insert那里的私有方法。

一种可能的实现是(未经测试):

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++;

}
Run Code Online (Sandbox Code Playgroud)