C# 如何加快chunky BlockingCollection的实现

C# 如何加快chunky BlockingCollection的实现,c#,.net,multithreading,performance,blockingcollection,C#,.net,Multithreading,Performance,Blockingcollection,我已经多次使用了来实现生产者/消费者模式,但是由于相关的开销,我在使用非常细粒度的数据时遇到了糟糕的性能。这通常迫使我通过对数据进行分块/分区来即兴创作,换句话说,使用BlockingCollection而不是BlockingCollection。这是一本书。这是可行的,但它丑陋且容易出错。最后,我在生产者和消费者都使用嵌套循环,我必须记住添加生产者工作负载结束时剩下的内容。因此我想到了实现一个chunkyBlockingCollection,它将在内部处理所有这些复杂问题,并将与现有的Bloc

我已经多次使用了来实现生产者/消费者模式,但是由于相关的开销,我在使用非常细粒度的数据时遇到了糟糕的性能。这通常迫使我通过对数据进行分块/分区来即兴创作,换句话说,使用
BlockingCollection
而不是
BlockingCollection
。这是一本书。这是可行的,但它丑陋且容易出错。最后,我在生产者和消费者都使用嵌套循环,我必须记住添加生产者工作负载结束时剩下的内容。因此我想到了实现一个chunky
BlockingCollection
,它将在内部处理所有这些复杂问题,并将与现有的
BlockingCollection
相同的简单接口外部化。我的问题是,我还没有达到复杂的手动分区的性能。我的最佳尝试仍然是为非常细粒度的数据(基本上只是整数值)支付大约+100%的性能税。因此,我想在这里介绍我到目前为止所做的工作,希望得到一个能帮助我缩小绩效差距的建议

我最好的尝试是使用,这样每个线程都可以在一个专用块上工作,从而消除对锁的任何需要

public class ChunkyBlockingCollection1<T>
{
    private readonly BlockingCollection<T[]> _blockingCollection;
    public readonly int _chunkSize;
    private readonly ThreadLocal<List<T>> _chunk;

    public ChunkyBlockingCollection1(int chunkSize)
    {
        _blockingCollection = new BlockingCollection<T[]>();
        _chunkSize = chunkSize;
        _chunk = new ThreadLocal<List<T>>(() => new List<T>(chunkSize), true);
    }

    public void Add(T item)
    {
        var chunk = _chunk.Value;
        chunk.Add(item);
        if (chunk.Count >= _chunkSize)
        {
            _blockingCollection.Add(chunk.ToArray());
            chunk.Clear();
        }
    }

    public void CompleteAdding()
    {
        var chunks = _chunk.Values.ToArray();
        foreach (var chunk in chunks)
        {
            _blockingCollection.Add(chunk.ToArray());
            chunk.Clear();
        }
        _blockingCollection.CompleteAdding();
    }

    public IEnumerable<T> GetConsumingEnumerable()
    {
        foreach (var chunk in _blockingCollection.GetConsumingEnumerable())
        {
            for (int i = 0; i < chunk.Length; i++)
            {
                yield return chunk[i];
            }
        }
    }
}

您可以尝试使用
\u chunk
的数组,而不是使用
List
。然后,您可以使用Interlocked.Increment在
Add
上增加下一个要填充的索引,当您的计数超过块的大小时,将其全部移动到阻塞集合并在锁中重置索引,当然。

以“我需要想法”开头的标题会引起错误类型的注意。@Nkosi似乎我的问题没有引起注意。@TheodorZoulias,你是否尝试使用
BlockingCollection[]
?@DmitryStepanov不,我没有。这将如何工作?@TheodorZoulias,看看这个()如果我能在
Add
方法中去掉
lock
,那将非常好,但我认为这是不可能的。如果没有
lock
I,则有可能在
BlockinCollection
中的数组的所有元素都被数据填充之前将其推入。当消费者线程处理时,会导致
NullReferenceException
。@TheodorZoulias,这是有道理的。要消除竞争条件,如果Interlocated.Increment返回的值超过区块大小,则可以旋转wait(使用Increment.CompareExchange)以等待,直到将区块移动到BlockingCollection,然后重置区块的当前索引的操作完成。值得一试!伙计,无锁编程很难!我用
联锁的实现更新了我的问题。不幸的是,结果不是很有希望。
public class ChunkyBlockingCollection2<T>
{
    private readonly BlockingCollection<T[]> _blockingCollection;
    public readonly int _chunkSize;
    private readonly List<T> _chunk;
    private readonly object _locker = new object();

    public ChunkyBlockingCollection2(int chunkSize)
    {
        _blockingCollection = new BlockingCollection<T[]>();
        _chunkSize = chunkSize;
        _chunk = new List<T>(chunkSize);
    }

    public void Add(T item)
    {
        lock (_locker)
        {
            _chunk.Add(item);
            if (_chunk.Count >= _chunkSize)
            {
                _blockingCollection.Add(_chunk.ToArray());
                _chunk.Clear();
            }
        }
    }

    public void CompleteAdding()
    {
        lock (_locker)
        {
            _blockingCollection.Add(_chunk.ToArray());
            _chunk.Clear();
        }
        _blockingCollection.CompleteAdding();
    }

    public IEnumerable<T> GetConsumingEnumerable()
    {
        foreach (var chunk in _blockingCollection.GetConsumingEnumerable())
        {
            for (int i = 0; i < chunk.Length; i++)
            {
                yield return chunk[i];
            }
        }
    }
}
public class ChunkyBlockingCollection3<T>
{
    private readonly BlockingCollection<(T[], int)> _blockingCollection;
    public readonly int _chunkSize;
    private T[] _array;
    private int _arrayCount;
    private int _arrayCountOfCompleted;
    private T[] _emptyArray;

    public ChunkyBlockingCollection3(int chunkSize)
    {
        _chunkSize = chunkSize;
        _blockingCollection = new BlockingCollection<(T[], int)>();
        _array = new T[chunkSize];
        _arrayCount = 0;
        _arrayCountOfCompleted = 0;
        _emptyArray = new T[chunkSize];
    }

    public void Add(T item)
    {
        while (true) // Spin
        {
            int count = _arrayCount;
            while (true) // Spin
            {
                int previous = count;
                count++;
                int result = Interlocked.CompareExchange(ref _arrayCount,
                    count, previous);
                if (result == previous) break;
                count = result;
            }
            var array = Interlocked.CompareExchange(ref _array, null, null);
            if (array == null) throw new InvalidOperationException(
                    "The collection has been marked as complete.");
            if (count <= _chunkSize)
            {
                // There is empty space in the array
                array[count - 1] = item;
                Interlocked.Increment(ref _arrayCountOfCompleted);
                break; // Adding is completed
            }
            if (count == _chunkSize + 1)
            {
                // Array is full. Push it to the BlockingCollection.
                while (Interlocked.CompareExchange(
                    ref _arrayCountOfCompleted, 0, 0) < _chunkSize) { } // Spin
                _blockingCollection.Add((array, _chunkSize));
                T[] newArray;
                while ((newArray = Interlocked.CompareExchange(
                    ref _emptyArray, null, null)) == null) { } // Spin
                Interlocked.Exchange(ref _array, newArray);
                Interlocked.Exchange(ref _emptyArray, null);
                Interlocked.Exchange(ref _arrayCountOfCompleted, 0);
                Interlocked.Exchange(ref _arrayCount, 0); // Unlock other threads
                Interlocked.Exchange(ref _emptyArray, new T[_chunkSize]);
            }
            else
            {
                // Wait other thread to replace the full array with a new one.
                while (Interlocked.CompareExchange(
                    ref _arrayCount, 0, 0) > _chunkSize) { } // Spin
            }
        }
    }

    public void CompleteAdding()
    {
        var array = Interlocked.Exchange(ref _array, null);
        if (array != null)
        {
            int count = Interlocked.Exchange(ref _arrayCount, -1);
            while (Interlocked.CompareExchange(
                ref _arrayCountOfCompleted, 0, 0) < count) { } // Spin
            _blockingCollection.Add((array, count));
            _blockingCollection.CompleteAdding();
        }
    }

    public IEnumerable<T> GetConsumingEnumerable()
    {
        foreach (var (array, count) in _blockingCollection.GetConsumingEnumerable())
        {
            for (int i = 0; i < count; i++)
            {
                yield return array[i];
            }
        }
    }
}