为什么ConcurrentDictionary.AddOrUpdate方法慢?

时间:2013-04-30 08:02:26

标签: c# multithreading concurrentdictionary

我正在研究线程安全的多值字典。在内部,此字典使用并发字典(.net 4.0)和自定义链接列表作为值。链接列表中添加了相同的关键项。问题是当我使用并发字典的AddOrUpdate方法(方法1)来插入项目时,与使用TryGetValue方法检查密钥是否存在时相比,代码运行速度有点慢然后在锁(方法2)中手动添加或更新值。使用第一种方法插入300万条记录需要大约20秒,而使用第二种方法在同一台机器上需要大约9.5秒(Intel i3第二代2.2 ghz和4 Gb ram)。肯定有一些我无法弄清楚的事情。

我还检查了并发字典的代码,但它似乎与我在锁中做的一样:

public TValue AddOrUpdate(TKey key, Func<TKey, TValue> addValueFactory, Func<TKey, TValue, TValue> updateValueFactory)
    {
        if (key == null) throw new ArgumentNullException("key"); 
        if (addValueFactory == null) throw new ArgumentNullException("addValueFactory");
        if (updateValueFactory == null) throw new ArgumentNullException("updateValueFactory"); 

        TValue newValue, resultingValue;
        while (true) 
        {
            TValue oldValue;
            if (TryGetValue(key, out oldValue))
            //key exists, try to update 
            {
                newValue = updateValueFactory(key, oldValue); 
                if (TryUpdate(key, newValue, oldValue)) 
                {
                    return newValue; 
                }
            }
            else //try add
            { 
                newValue = addValueFactory(key);
                if (TryAddInternal(key, newValue, false, true, out resultingValue)) 
                { 
                    return resultingValue;
                } 
            }
        }
    }

这是线程安全多值字典的代码(注释方法2,取消注释以检查差异)。

更新:还有删除,添加和其他方法,我还没有在下面粘贴。

class ValueWrapper<U, V>
{
    private U _key;
    private V _value;

    public ValueWrapper(U key, V value)
    {
        this._key = key;
        this._value = value;
    }

    public U Key
    {
        get { return _key; }
    }

    public V Value
    {
        get { return _value; }
        set { _value = value; }
    }
}

class LinkNode<Type>
{
    public LinkNode(Type data)
    {
        Data = data;
    }
    public LinkNode<Type> Next;
    public Type Data;
}

public class SimpleLinkedList<T> 
{
    #region Instance Member Variables
    private LinkNode<T> _startNode = null;
    private LinkNode<T> _endNode = null;
    private int _count = 0;

    #endregion

    public void AddAtLast(T item)
    {
        if (_endNode == null)
            _endNode = _startNode = new LinkNode<T>(item);
        else
        {
            LinkNode<T> node = new LinkNode<T>(item);
            _endNode.Next = node;
            _endNode = node;
        }

        _count++;
    }

    public T First
    {
        get { return _startNode == null ? default(T) : _startNode.Data; }
    }

    public int Count
    {
        get { return _count; }
    }

}

class MultiValThreadSafeDictionary<U, T>
{
    private ConcurrentDictionary<U, SimpleLinkedList<ValueWrapper<U, T>>> _internalDictionary;

    private ReaderWriterLockSlim _slimLock = new ReaderWriterLockSlim();


    public MultiValThreadSafeDictionary()
    {
        _internalDictionary = new ConcurrentDictionary<U, SimpleLinkedList<ValueWrapper<U, T>>>(2, 100);
    }

    public T this[U key]
    {
        get
        {
            throw new NotImplementedException();
        }
        set
        {
            /* ****Approach 1 using AddOrUpdate**** */


            _internalDictionary.AddOrUpdate(key, (x) =>
            {
                SimpleLinkedList<ValueWrapper<U, T>> list = new SimpleLinkedList<ValueWrapper<U, T>>();
                ValueWrapper<U, T> vw = new ValueWrapper<U, T>(key, value);
                list.AddAtLast(vw);
                //_internalDictionary[key] = list;

                return list;
            },

            (k, existingList) =>
            {
                try
                {
                    _slimLock.EnterWriteLock();

                    if (existingList.Count == 0)
                    {
                        ValueWrapper<U, T> vw = new ValueWrapper<U, T>(key, value);
                        existingList.AddAtLast(vw);
                    }
                    else
                        existingList.First.Value = value;

                    return existingList;
                }
                finally
                {
                    _slimLock.ExitWriteLock();
                }
            });


            /* ****Approach 2 not using AddOrUpdate**** */

            /*
            try
            {
                _slimLock.EnterWriteLock();

                SimpleLinkedList<ValueWrapper<U, T>> list;
                if (!_internalDictionary.TryGetValue(key, out list))
                {
                    list = new SimpleLinkedList<ValueWrapper<U, T>>();
                    ValueWrapper<U, T> vw = new ValueWrapper<U, T>(key, value);

                    list.AddAtLast(vw);

                    _internalDictionary[key] = list;
                    //_iterator.AddAtLast(vw);
                    return;
                }

                if (list.Count == 0)
                {
                    ValueWrapper<U, T> vw = new ValueWrapper<U, T>(key, value);
                    list.AddAtLast(vw);
                    //_iterator.AddAtLast(vw);
                }
                else
                    list.First.Value = value;
            }
            finally
            {
                _slimLock.ExitWriteLock();
            }
            */

        }
    }
}

测试代码仅插入项目,所有项目都使用唯一键。它如下。

MultiValThreadSafeDictionary<string, int> testData = new MultiValThreadSafeDictionary<string, int>();

    Task t1 = new Task(() =>
        {
            for (int i = 0; i < 1000000; i++)
            {
                testData[i.ToString()] = i;
            }
        }
    );

    Task t2 = new Task(() =>
    {
        for (int i = 1000000; i < 2000000; i++)
        {
            testData[i.ToString()] = i;
        }
    }
    );

    Task t3 = new Task(() =>
    {
        for (int i = 2000000; i < 3000000; i++)
        {
            testData[i.ToString()] = i;
        }
    }
    );

    Stopwatch watch = new Stopwatch();
    watch.Start();

    t1.Start();
    t2.Start();
    t3.Start();

    t1.Wait();
    t2.Wait();
    t3.Wait();

    watch.Stop();

    Console.WriteLine("time taken:" + watch.ElapsedMilliseconds);

更新1:

基于'280Z28'的答案,我正在改写这个问题。为什么GetOrAdd和'my'方法花费几乎相同的时间,在我的方法中我采取额外的锁定并且也调用TryAndGet方法。为什么AddOrUpdate与AddOrGet相比花费了两倍的时间。所有方法的代码如下:

ConcurrentDictionary(.net 4)中的

GetOrAdd和AddOrUpdate方法具有以下代码:

public TValue GetOrAdd(TKey key, TValue value)
{
    if (key == null) throw new ArgumentNullException("key");
    TValue resultingValue;
    TryAddInternal(key, value, false, true, out resultingValue); 
    return resultingValue; 
}

public TValue AddOrUpdate(TKey key, Func<TKey, TValue> addValueFactory, Func<TKey, TValue, TValue> updateValueFactory)
{
    if (key == null) throw new ArgumentNullException("key"); 
    if (addValueFactory == null) throw new ArgumentNullException("addValueFactory");
    if (updateValueFactory == null) throw new ArgumentNullException("updateValueFactory"); 

    TValue newValue, resultingValue;
    while (true) 
    {
        TValue oldValue;
        if (TryGetValue(key, out oldValue))
        //key exists, try to update 
        {
            newValue = updateValueFactory(key, oldValue); 
            if (TryUpdate(key, newValue, oldValue)) 
            {
                return newValue; 
            }
        }
        else //try add
        { 
            newValue = addValueFactory(key);
            if (TryAddInternal(key, newValue, false, true, out resultingValue)) 
            { 
                return resultingValue;
            } 
        }
    }
}

我的代码中的GetOrAdd使用如下(花费9秒):

SimpleLinkedList<ValueWrapper<U, T>> existingList = new SimpleLinkedList<ValueWrapper<U, T>>();
existingList = _internalDictionary.GetOrAdd(key, existingList);
try
{
    _slimLock.EnterWriteLock();

    if (existingList.Count == 0)
    {
        ValueWrapper<U, T> vw = new ValueWrapper<U, T>(key, value);
        existingList.AddAtLast(vw);
    }
    else
        existingList.First.Value = value;
}
finally
{
    _slimLock.ExitWriteLock();
}

AddOrUpdate使用如下(所有添加都需要20秒,没有更新)。如其中一个答案所述,这种方法不适合更新。

_internalDictionary.AddOrUpdate(key, (x) =>
{
    SimpleLinkedList<ValueWrapper<U, T>> list = new SimpleLinkedList<ValueWrapper<U, T>>();
    ValueWrapper<U, T> vw = new ValueWrapper<U, T>(key, value);
    list.AddAtLast(vw);
    return list;
},

(k, existingList ) =>
{
    try
    {
        _slimLock.EnterWriteLock();

        if (existingList.Count == 0)
        {
            ValueWrapper<U, T> vw = new ValueWrapper<U, T>(key, value);
            existingList.AddAtLast(vw);
        }
        else
            existingList.First.Value = value;

        return existingList;
    }
    finally
    {
        _slimLock.ExitWriteLock();
    }
});

没有AddOrGet和AddOrUpdate的代码如下(花费9.5秒):

try
{
    _slimLock.EnterWriteLock();

    VerySimpleLinkedList<ValueWrapper<U, T>> list;
    if (!_internalDictionary.TryGetValue(key, out list))
    {
        list = new VerySimpleLinkedList<ValueWrapper<U, T>>();
        ValueWrapper<U, T> vw = new ValueWrapper<U, T>(key, value);

        list.AddAtLast(vw);

        _internalDictionary[key] = list;
        return;
    }

    if (list.Count == 0)
    {
        ValueWrapper<U, T> vw = new ValueWrapper<U, T>(key, value);
        list.AddAtLast(vw);
    }
    else
        list.First.Value = value;
}
finally
{
    _slimLock.ExitWriteLock();
}

2 个答案:

答案 0 :(得分:1)

您不应该将AddOrUpdate用于此代码。这非常清楚,因为您的更新方法实际上永远不会更新存储在ConcurrentDictionary中的值 - 它始终会返回existingList参数不变。相反,你应该做以下事情。

SimpleLinkedList<ValueWrapper<U, T>> list = _internalDictionary.GetOrAdd(key, CreateEmptyList);
// operate on list here

...

private static SimpleLinkedList<ValueWrapper<U, T>> CreateEmptyList()
{
    return new SimpleLinkedList<ValueWrapper<U, T>>();
}

答案 1 :(得分:0)

  

字典上的读取操作以无锁方式执行。   如http://msdn.microsoft.com/en-us/library/dd287191.aspx

中所述

AddOrUpdate的实现是使用细粒度锁定,以便检查项目是否已经存在,但是当您第一次自己阅读时,无锁读取它会更快,并且通过这样做可以减少现有项目所需的锁定。