【问题标题】:Why can't I reach 100% CPU utilization with my parallel tasks code?为什么我的并行任务代码不能达到 100% 的 CPU 利用率?
【发布时间】:2020-09-17 19:29:56
【问题描述】:
private static async Task<int> SumParallel()
        {
            var intList = Enumerable.Range(1, 1000_000_000);
            int count = intList.Count();
            int total = 0;
            for(int i = 1; i < 5; i++)
            {
                int skip = ((i - 1) * count) / 4;
                int take = count / 4;
                Interlocked.Add(ref total,
                    await GetSum(intList, skip, take));
            }
            return total;
        }

private static async Task<int> GetSum(IEnumerable<int> list, int skip, int take)
        {
            return await Task.Run(() =>
             {
                 int temp = 0;
                 foreach(int n in list.Skip(skip).Take(take))
                 {
                     if (n % 2 == 0)
                         temp -= n;
                     else
                         temp += n;
                 }
                 return temp;
             });
        }

我正在尝试执行计算密集型任务,只是为了练习任务并行库。所以,我写了这段代码。如果我尝试使用 Parallel Linq 计算相同的总和,我可以看到 CPU 利用率达到 100%,如下所示:

int sum = Enumerable.Range(1, 1000_000_000)
                .AsParallel()
                .Select(i => i % 2 == 0 ? -i : i).Sum();

手动并行任务代码 = 10 秒时间,cpu = 仅 25%

Parallel Linq 代码 = 18 秒时间,cpu = 100%(仍然需要 18 秒)

没有并行的 Linq 代码:14 秒,cpu = 25% 而已

为什么会这样?当我同时启动 4 个线程时,为什么我的 cpu 利用率没有达到 100%?与非并行代码(只有一个线程)相比,它只有 25%。我真的是并行运行 4 个线程吗?

我的笔记本电脑是 Core i3 - 2 核 = 4 个逻辑处理器

【问题讨论】:

  • 你可以看看Task.WhenAll()

标签: c# multithreading


【解决方案1】:

SumParallel 没有执行并行工作,因为它按顺序创建并等待每个任务:

for(int i = 1; i < 5; i++) {
    //...
    await GetSum(intList, skip, take)

要使其并行,您必须启动所有五个任务,然后 await 全部以 await Task.WhenAll(tasks); 完成。

至于 Parallel Linq 比标准 Linq 慢,那是因为你的工作量太细了。结果,并行性的开销抵消了并行执行的任何好处。换句话说,将项目分配给线程并收集结果比实际计算 (n % 2) 需要更多的工作。为了充分利用并行性,您的工作负载必须是庞大的。

【讨论】:

  • 谢谢,这解决了我的问题。现在,在 100% 的 CPU 利用率下完成需要 5 秒。
  • @teenup 请注意,.Skip(skip).Take(take) 模式可能不是分区数据的有效方式,具体取决于您运行代码的平台。我认为.NET Core 已经针对这种(显然是常见的)场景实现了特定的优化,但是.NET Framework 并没有进行优化,并且会遵循将List 中的元素一一枚举的缓慢路径,从首先。
  • 实际上,如果我修改并行工作的线程数,我会得到不同的总和。我不知道为什么。我为此发布了另一个问题。可以的话请看一下。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2010-09-20
  • 2017-03-17
  • 2021-02-25
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多