Parallel.ForEach(...) 的 CPU 使用率较低

mpi*_*ium 4 .net c# multithreading .net-core

我正在尝试并行化任务,但没有获得良好的性能。我最多只能得到 50% 的 CPU 使用率。

下面是一个玩具示例,我构建了一个随机单词列表的列表,然后遍历每个单词并反转它的字符串。反转部分是我试图并行化的部分。

对我来说,这看起来像是一个 CPU 限制问题,所以我不太明白为什么 CPU 使用率如此低。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace Papa
{
    class Program
    {
        static void Main(string[] args)
        {
            // Build source
            var random = new Random();
            var alphabet = "abcdefghijklmnopqrstuvwxyz";

            Console.WriteLine("Building data source");
            var source = new List<string[]>();
            foreach (var i in Enumerable.Range(0, 5000000))
            {
                var words = random.Next(3, 50);
                var line = new List<string>();

                for (var j = 0; j < words; ++j)
                {
                    line.Add(
                        string.Concat(
                            Enumerable
                                .Range(0, random.Next(3, 9))
                                .Select(w => alphabet[random.Next(0, alphabet.Length - 1)])
                        )
                    );
                }

                source.Add(line.ToArray());
            }

            // Process source
            Console.WriteLine("Processing source");
            var processed = new List<string[]>();
            Parallel.ForEach(
                source,
                () => new List<string[]>(),
                (line, loop, local) =>
                {
                    var processedLine = new List<string>();
                    for (var i = 0; i < line.Count(); ++i)
                    {
                        processedLine.Add(string.Concat(line[i].Reverse()));
                    }

                    local.Add(processedLine.ToArray());
                    return local;
                },
                partitionResult =>
                {
                    lock (processed)
                    {
                        processed.AddRange(partitionResult);
                    }
                });
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

Ale*_*lex 6

Parallel.ForEach将使用调度程序默认提供的尽可能多的线程(参考)。请注意,文档指出:

在分区器上执行 foreach(在 Visual Basic 中为 For Each)操作,其中迭代可以并行运行,并且可以配置循环选项。

因此,可能的情况是它似乎不适合默认选项。

运行代码后,我发现以下内容(当点击并行部分时):

在此输入图像描述

正如您所看到的,它已经使用了 15Gb 内存,但它在所有内核上运行。我很确定您会遇到内存/GC 瓶颈而不是 CPU 瓶颈。

我运行了一些额外的性能分析。这是我发现的(String::Concat可以忽略,因为它是初始化的一部分):

在此输入图像描述

第 45 行占用了大约 50% 的 CPU 时间。

在此输入图像描述

我对代码进行了一些修改并进行了以下更改:

Parallel.ForEach(
             source,
             () => new List<string[]>(),
             (line, loop, local) =>
             {
                 var length = line.Length;
                 var processedLine = new string[length];
                 for (var i = 0; i < length; ++i)
                 {
                     processedLine[i] = line[i].Reverse() + "";
                 }

                 local.Add(processedLine);
                 return local;
             },
             partitionResult =>
             {
                 lock (processed)
                 {
                     processed.AddRange(partitionResult);
                 }
             });
Run Code Online (Sandbox Code Playgroud)

当数据大小加倍(10000000~32Gb 数据)时,这至少显示了 CPU 使用率的一个小峰值,但它在不到7 秒的时间内完成,所以我不太确定 TaskManager 是否真的正确地获得了峰值。尽管如此,这也不能解决问题,GC 也疯狂运行。

我的“最终”结论是,它是以下之一(或多个):

  • GC
  • 记忆
  • 缓存未命中

这限制了你。

在我看来,这是一个很好的例子,说明即使您可以使用x线程,并行化也不会产生x倍的性能。

  • 大量的内存可能是由于创建单词时通过串联创建的字符串数量以及反转单词时的并行 foreach 中创建的字符串数量所致。你最好使用 char[] 并进行就地反转。这将严重减少字符串创建的数量。 (2认同)