并发收集尽可能快地添加,删除和查找最高

时间:2015-07-25 14:14:06

标签: c# .net concurrency concurrent-programming concurrentdictionary

我正在C#.NET中进行一些繁重的计算,并且在并行执行这些计算时。我必须在集合中收集一些数据,但由于内存有限,我无法收集所有结果,所以我只保存最好的的。

这些计算必须尽可能快,因为它们已经花费了太多时间。经过优化后,我发现最慢的是我的ConcurrentDictionary集合。我想知道我是否应该切换到更快的添加,删除和找到最高(可能是一个排序的集合)的东西,只是使用锁为我的主要操作或我可以使用ConcurrentColletion做一些好事,并加快一点

这是我的实际代码,我知道这很糟糕,因为这个巨大的锁定,但没有它我似乎失去了一致性,我的很多删除尝试都失败了。

 public class SignalsMultiValueConcurrentDictionary : ConcurrentDictionary<double, ConcurrentBag<Signal>>
{
    public  int Limit { get; set; }
    public double WorstError { get; private set; }

    public SignalsDictionaryState TryAddSignal(double key, Signal signal, out Signal removed)
    {
        SignalsDictionaryState state;
        removed = null;

        if (this.Count >= Limit && signal.AbsoluteError > WorstError)
            return SignalsDictionaryState.NoAddedNoRemoved;

        lock (this)
        {
            if (this.Count >= Limit)
            {
                ConcurrentBag<Signal> signals;
                if (TryRemove(WorstError, out signals))
                {
                    removed = signals.FirstOrDefault();
                    state = SignalsDictionaryState.AddedAndRemoved;
                }
                else
                    state = SignalsDictionaryState.AddedFailedRemoved;
            }
            else
                state = SignalsDictionaryState.AddedNoRemoved;

            this.Add(key, signal);
            WorstError = Keys.Max();
        }
        return state;
    }

    private void Add(double key, Signal value)
    {
        ConcurrentBag<Signal> values;
        if (!TryGetValue(key, out values))
        {
            values = new ConcurrentBag<Signal>();
            this[key] = values;
        }

        values.Add(value);
    }
}

另请注意,因为我使用信号的绝对误差,有时(应该非常罕见)我在一个键上存储了多个值。

我的计算中使用的唯一操作是TryAddSignal因为它符合我的要求 - &gt;如果我有更多的signlas而不是limit,那么它将删除具有最高错误的信号并添加新信号。

由于我在计算开始时设置了Limit属性,因此我不需要可调整大小的集合。

这里的主要问题是即使没有那么大的锁定,Keys.Max也有点太慢了。也许我需要其他收藏品?

3 个答案:

答案 0 :(得分:2)

lock声明至少是可疑的。如果你说Keys.Max()很慢,那么更容易改进就是逐步计算最大值。只有在删除密钥后才需要刷新它:

//...
if (TryRemove(WorstError, out signals))
{
    WorstError = Keys.Max();

//...

WorstError = Math.Max(WorstError, key);

答案 1 :(得分:2)

Keys.Max()是杀手。那是O(N)。如果你这样做,就不需要字典了。

您无法逐步计算最大值,因为您要添加删除。因此,您最好使用为此创建的数据结构。树通常是。我相信BCL有SortedListSortedSet以及SortedDictionary。其中一个是基于一棵快树。它有最小和最大操作。

或者,使用具有优先级队列的.NET集合库。

Bug:添加很有趣。您可能会覆盖非空集合。

答案 2 :(得分:1)

我最终做的是按照@usr的建议实现基于二叉树的Heap。我的最终集合不是并发的,而是同步的(我使用了锁)。我检查了性能思想,它完成了足够快的工作。 这是伪代码:

public class SynchronizedCollectionWithMaxOnTop
{
    double Max => _items[0].AbsoluteError;

    public ItemChangeState TryAdd(Item item, out Item removed)
    {
        ItemChangeState state;
        removed = null;

        if (_items.Count >= Limit && signal.AbsoluteError > Max)
            return ItemChangeState.NoAddedNoRemoved;

        lock (this)
        {
            if (_items.Count >= Limit)
            {
                removed = Remove();
                state = ItemChangeState.AddedAndRemoved;
            }
            else
                state = ItemChangeState.AddedNoRemoved;

            Insert(item);
        }
        return state;
    }

    private void Insert(Item item)
    {
        _items.Add(item);
        HeapifyUp(_items.Count - 1);
    }

    private void Remove()
    {
        var result = new Item(_items[0]);

        var lastIndex = _items.Count - 1;

        _items[0] = _items[lastIndex];
        _items.RemoveAt(lastIndex);

        HeapifyDown(0);

        return result;
    }
}