c# - Parallel.ForEach 可以导致 "Out Of Memory"异常,如果使用可枚举的大对象

标签 c# out-of-memory task-parallel-library large-data

我正在尝试将图像存储在数据库中的数据库迁移到数据库中指向硬盘驱动器上文件的记录。我正在尝试使用 Parallel.ForEach加快进程using this method查询出数据。

但是,我注意到我得到了一个 OutOfMemory异常(exception)。我知道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
        }
    }
}

最佳答案

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 上运行您的示例应用程序,无论我设置的最大并行度如何,我的 CPU 使用率大约为 20%。为什么是这样?分配的内存太多,以至于垃圾收集器阻塞了线程。应用程序是资源绑定(bind)的,资源是内存。

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

如果您的任务不受 CPU 限制或 CPU 利用率不随最大并行度线性扩展,那么您应该建议 Parallel.ForEach 不要一次启动太多任务。最简单的方法是指定一个数字,该数字允许重叠的 I/O 绑定(bind)任务具有一定的并行性,但又不会过多到超出本地计算机对资源的需求或使任何远程服务器负担过重。需要反复试验才能获得最佳结果:

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

关于c# - Parallel.ForEach 可以导致 "Out Of Memory"异常,如果使用可枚举的大对象,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/6977218/

相关文章:

c# - 简单企业库控制台应用程序拒绝编译

c# - 如何在 C# 中监视剪贴板更改?

c# - (新格式)visual studio 项目中的可选 appsettings.local.json

clojure - 在 Clojure 中处理大文件时出现 OutOfMemory 错误

java - 为什么无限对象创建不会抛出 OutOfMemoryError?

c# - 在另一个线程中运行 WPF 控件

C# - 无法在 WinForms 的列表框中执行键值对

memory-management - Linux 内存过量使用详情

c# - TaskFactory,结束时开始一个新任务

c# - async wait 与 TaskFactory.StartNew 和 WaitAll