AsParallel()和内部缓冲区大小

时间:2011-12-07 15:53:37

标签: .net multithreading plinq

如何限制AsParallel()预先读取并放入其内部缓冲区的项目数量?

以下是一个例子:

int returnedCounter;

IEnumerable<int> Enum()
{
    while (true)
        yield return Interlocked.Increment(ref returnedCounter);
}

[TestMethod]
public void TestMethod1()
{
    foreach (var i in Enum().AsParallel().Select(a => a))
    {
        Thread.Sleep(3000);
        break;
    }
    Console.WriteLine(returnedCounter);
}

我消耗1项,睡觉,停止枚举。它在我的机器上打印526400。在我的真实项目中,每个项目分配数千KB。 AsParallel()预先读取了很多项目,导致非常糟糕的内存消耗和CPU浪费。

使用WithMergeOptions(ParallelMergeOptions.NotBuffered)有点帮助。它打印4544.但它对我来说仍然太多了。

在Enum()中等待冻结主线程中的循环。

2 个答案:

答案 0 :(得分:4)

关于Partitioners的另一个问题!

在您的情况下,您必须找到/写一个一次只需要一个项目的分区程序。

这是关于Custom Partitioners

的文章

<强>更新

我只记得我看到SingleItemPartitioner实施的位置:它位于ParallelExtensionsExtras项目中Samples for Parallel Programming with the .NET Framework

我也刚读过你的测试代码。我可能应该第一次这样做!

此代码:

Enum().AsParallel().Select(a => a)

表示:接受Enum()并尽可能快地枚举它,并返回一个新的IEnumerable<int>

因此,foreach不会从Enum()中提取项目 - 它会从linq语句创建的新IEnumerable<int>中提取项目。

此外,您的foreach在主线程上运行,因此每个项目的工作都是单线程的。

如果您想并行运行,但只在需要时生成一个项目,请尝试:

Parallel.ForEach( SingleItemPartitioner.Create( Enum() ), ( i, state ) =>
    {
        Thread.Sleep( 3000 );
        state.Break();
    }

答案 1 :(得分:0)

找到了解决方法。

首先,让我澄清一下原来的问题。我需要一个可以在无限序列上运行的可管理流水线。管道是:

  1. 同步读取序列:Enum()
  2. 并行处理项目:AsParallel().Select(a => a)
  3. 继续进行同步处理:foreach body
  4. 步骤3可能会暂停管道。这是由Sleep()模仿的。问题是当pipleine暂停时,第2步会提取太多元素。  PLinq必须有一些内部队列。无法显式配置队列大小。但是大小取决于ParallelMergeOptionsParallelMergeOptions.NotBuffered会降低队列大小,但对我来说大小仍然太大。

    我的解决方法是知道正在处理的项目数量,达到限制时停止并行处理,再次启动管道时重新启动并行处理。

    int sourceCounter;
    
    IEnumerable<int> SourceEnum() // infinite input sequence
    {
        while (true)
            yield return Interlocked.Increment(ref sourceCounter);
    }
    
    [TestMethod]
    public void PlainPLinq_PausedConsumtionTest()
    {
        sourceCounter = 0;
        foreach (var i in SourceEnum().AsParallel().WithMergeOptions(ParallelMergeOptions.NotBuffered).Select(a => a))
        {
            Thread.Sleep(3000);
            break;
        }
        Console.WriteLine("fetched from source sequence: {0}", sourceCounter); // prints 4544 on my machine
    }
    
    [TestMethod]
    public void MyParallelSelect_NormalConsumtionTest()
    {
        sourceCounter = 0;
        foreach (var i in MyParallelSelect(SourceEnum(), 64, a => a))
        {
            if (sourceCounter > 1000000)
                break;
        }
        Console.WriteLine("fetched from source sequence: {0}", sourceCounter);
    }
    
    [TestMethod]
    public void MyParallelSelect_PausedConsumtionTest()
    {
        sourceCounter = 0;
        foreach (var i in MyParallelSelect(SourceEnum(), 64, a => a))
        {
            Thread.Sleep(3000);
            break;
        }
        Console.WriteLine("fetched from source sequence: {0}", sourceCounter);
    }
    
    class DataHolder<D> // reference type to store class or struct D
    {
        public D Data;
    }
    
    static IEnumerable<DataHolder<T>> FetchSourceItems<T>(IEnumerator<T> sourceEnumerator, DataHolder<int> itemsBeingProcessed, int queueSize)
    {
        for (; ; )
        {
            var holder = new DataHolder<T>();
            if (Interlocked.Increment(ref itemsBeingProcessed.Data) > queueSize)
            {
                // many enought items are already being processed - stop feeding parallel processing
                Interlocked.Decrement(ref itemsBeingProcessed.Data);
                yield break;
            }
            if (sourceEnumerator.MoveNext())
            {
                holder.Data = sourceEnumerator.Current;
                yield return holder;
            }
            else
            {
                yield return null; // return null DataHolder to indicate EOF
                yield break;
            }
        }
    }
    
    IEnumerable<OutT> MyParallelSelect<T, OutT>(IEnumerable<T> source, int queueSize, Func<T, OutT> selector)
    {
        var itemsBeingProcessed = new DataHolder<int>();
        using (var sourceEnumerator = source.GetEnumerator())
        {
            for (;;) // restart parallel processing
            {
                foreach (var outData in FetchSourceItems(sourceEnumerator, itemsBeingProcessed, queueSize).AsParallel().WithMergeOptions(ParallelMergeOptions.NotBuffered).Select(
                    inData => inData != null ? new DataHolder<OutT> { Data = selector(inData.Data) } : null))
                {
                    Interlocked.Decrement(ref itemsBeingProcessed.Data);
                    if (outData == null)
                        yield break; // EOF reached
                    yield return outData.Data;
                }
            }
        }
    }