【问题标题】:Parallel.ForEach performanceParallel.ForEach 性能
【发布时间】:2018-03-15 01:28:57
【问题描述】:

我正在使用Parallel.ForEach 提取一堆压缩文件并将它们复制到另一台机器上的共享文件夹中,然后启动BULK INSERT 进程。这一切都很好,但我注意到,一旦出现一些大文件,就不会启动新任务。我认为这是因为某些文件比其他文件花费的时间更长,TPL 开始缩小,并停止创建新任务。我已将 MaxDegreeOfParallelism 设置为合理的数字 (8)。当我查看 CPU 活动时,我可以看到,大多数时候 SQL Server 机器低于 30%,当它位于单个 BULK INSERT 任务上时甚至更少。我认为它可以做更多的工作。我可以以某种方式强制 TPL 创建更多同时处理的任务吗?

【问题讨论】:

  • 会不会是所有线程都在等待访问同一个驱动器?
  • 访问文件和更新数据库本质上是 IO 绑定的,并行执行只会导致共享资源的争用。执行此类 ETL 有很多选择。
  • 与使用并行处理相比,您可能会发现使用异步更好,这样线程就不会阻塞 IO 并且可以用于其他任务。
  • @pep 我已将每个任务的Thread.CurrentThread.ManagedThreadId 添加到我们的控制台输出中,它们都是独一无二的。
  • @UrbanEsc 如果您没有使用异步/等待模式,那么它可能不是异步的。异步的想法不是关于在单独的线程上运行的东西,而是关于在 IO 发生时线程没有被阻塞导致它们什么都不做而不是其他工作。

标签: c# .net task-parallel-library parallel.foreach


【解决方案1】:

原因很可能是Parallel.ForEach 默认处理项目的方式。如果您在数组或实现IList 的东西上使用它(以便总长度和索引器可用)-它将分批拆分整个工作负载。然后单独的线程将处理每个批次。这意味着如果批次具有不同的“大小”(大小是指处理它们的时间)-“小”批次将更快地完成。

例如,我们看一下这段代码:

var delays = Enumerable.Repeat(100, 24).Concat(Enumerable.Repeat(2000, 4)).ToArray();
Parallel.ForEach(delays, new ParallelOptions() {MaxDegreeOfParallelism = 4}, d =>
{
    Thread.Sleep(d);
    Console.WriteLine("Done with " + d);
});

如果您运行它,您将看到所有“100”(快速)项目都被快速并行处理。但是,所有“2000”(慢)项最终都被逐一处理,根本没有任何并行性。那是因为所有“慢”项目都在同一批次中。工作负载分为 4 批 (MaxDegreeOfParallelism = 4),前 3 批仅包含快速项目。他们很快就完成了。最后一批包含所有慢项,因此专用于该批的线程将逐个处理它们。

您可以通过确保项目均匀分布(以便“慢”项目不会在源集合中全部在一起)或例如使用自定义分区器来“解决”您的情况:

var delays = Enumerable.Repeat(100, 24).Concat(Enumerable.Repeat(2000, 4)).ToArray();
var partitioner = Partitioner.Create(delays, EnumerablePartitionerOptions.NoBuffering);
Parallel.ForEach(partitioner, new ParallelOptions {MaxDegreeOfParallelism = 4}, d =>
{
    Thread.Sleep(d);
    Console.WriteLine("Done with " + d);
});

NoBuffering 确保一次拿一件物品,因此避免了这个问题。

使用其他方式并行化您的工作(例如 SemaphoreSlimBlockingCollection)也是一种选择。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2019-08-06
    • 1970-01-01
    • 1970-01-01
    • 2023-01-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-01-15
    相关资源
    最近更新 更多