一尘不染

如果使用可枚举的大对象,Parallel.ForEach可能会导致“内存不足”异常

c#

我正在尝试将数据库中存储了图像的数据库迁移到数据库中指向硬盘驱动器上文件的记录。我试图使用这种方法Parallel.ForEach加速查询数据的过程。

但是,我注意到我遇到了OutOfMemory异常。我知道Parallel.ForEach将查询一批可枚举的对象,以减少开销(如果有一个用于将查询间隔开)(因此,如果您一次执行一堆查询而不是将它们间隔开,那么您的源将更有可能将下一条记录缓存在内存中出来)。问题是由于我返回的记录之一是1-4Mb字节数组,缓存导致整个地址空间用尽(程序必须以x86模式运行,因为目标平台将是32位机)

有什么方法可以禁用缓存或使TPL的缓存更小吗?


这是显示问题的示例程序。这必须在x86模式下进行编译,以显示问题,如果它花费很长时间或在您的计算机上没有发生,则增大了阵列的大小(我发现1 << 20在我的计算机上花费了大约30秒的时间,并且4 << 20几乎是瞬时的)

class Program
{

    static void Main(string[] args)
    {
        Parallel.ForEach(CreateData(), (data) =>
            {
                data[0] = 1;
            });
    }

    static IEnumerable<byte[]> CreateData()
    {
        while (true)
        {
            yield return new byte[1 << 20]; //1Mb array
        }
    }
}

阅读 521

收藏
2020-05-19

共1个答案

一尘不染

Parallel.ForEach 仅当任务受CPU限制并且线性扩展时, 默认选项 才可以正常工作
。当任务受CPU限制时,一切都将正常运行。如果您具有四核并且没有其他进程在运行,则Parallel.ForEach使用所有四个处理器。如果您具有四核,并且计算机上的某些其他进程正在使用一个完整的CPU,则Parallel.ForEach大约使用三个处理器。

但是,如果任务不受CPU限制,则Parallel.ForEach继续启动任务,努力使所有CPU保持忙碌状态。但是,无论并行执行多少任务,总是有更多未使用的CPU功能,因此它会继续创建任务。

如何判断您的任务是否受CPU限制?希望只是通过检查。如果要分解素数,这是显而易见的。但是其他情况并不那么明显。判断任务是否受CPU限制的经验方法是限制最大并行度,ParallelOptions.MaximumDegreeOfParallelism并观察程序的行为。如果您的任务是CPU密集型的,那么您应该在四核系统上看到这样的模式:

  • ParallelOptions.MaximumDegreeOfParallelism = 1:使用一个完整的CPU或25%的CPU利用率
  • ParallelOptions.MaximumDegreeOfParallelism = 2:使用两个CPU或50%的CPU利用率
  • ParallelOptions.MaximumDegreeOfParallelism = 4:使用所有CPU或100%CPU利用率

如果行为如此,则可以使用默认Parallel.ForEach选项并获得良好的效果。线性的CPU利用率意味着良好的任务调度。

但是,如果我在Intel
i7上运行示例应用程序,则无论我设置的最大并行度如何,我都会获得约20%的CPU利用率。为什么是这样?由于分配了太多内存,垃圾回收器阻塞了线程。应用程序是资源绑定的,资源是内存。

同样,对数据库服务器执行长时间运行查询的I /
O绑定任务也永远无法有效利用本地计算机上可用的所有CPU资源。并且在这种情况下,任务计划程序无法“知道何时停止”开始新任务。

如果您的任务不受CPU限制,或者CPU利用率不能以最大并行度线性扩展,则应建议Parallel.ForEach不要一次启动太多任务。最简单的方法是指定一个数字,该数字允许对重叠的I
/ O绑定任务进行某种并行处理,但又不能过多,以至于使本地计算机对资源的需求不堪重负,或者使任何远程服务器负担过多。反复试验才能获得最佳结果:

static void Main(string[] args)
{
    Parallel.ForEach(CreateData(),
        new ParallelOptions { MaxDegreeOfParallelism = 4 },
        (data) =>
            {
                data[0] = 1;
            });
}
2020-05-19