如果有的话,Parallel.Invoke可以提供最小的性能提升

gok*_*ter 0 .net c# performance task-parallel-library

我写了一个简单的控制台应用程序,以测试基于Microsoft在msdn上的示例的Parallel.Invoke的性能:

public static void TestParallelInvokeSimple()
    {
        ParallelOptions parallelOptions = new ParallelOptions { MaxDegreeOfParallelism = 1 }; // 1 to disable threads, -1 to enable them
        Parallel.Invoke(parallelOptions,
            () =>
                {
                    Stopwatch sw = new Stopwatch();
                    sw.Start();
                    Console.WriteLine("Begin first task...");
                    List<string> objects = new List<string>();
                    for (int i = 0; i < 10000000; i++)
                    {
                        if (objects.Count > 0)
                        {
                            string tempstr = string.Join("", objects.Last().Take(6).ToList());
                            objects.Add(tempstr + i);
                        }
                        else
                        {
                            objects.Add("START!");
                        }
                    }
                    sw.Stop();
                    Console.WriteLine("End first task... {0} seconds", sw.Elapsed.TotalSeconds);
                },
            () =>
                {
                    Stopwatch sw = new Stopwatch();
                    sw.Start();
                    Console.WriteLine("Begin second task...");
                    List<string> objects = new List<string>();
                    for (int i = 0; i < 10000000; i++)
                    {
                        objects.Add("abc" + i);
                    }
                    sw.Stop();
                    Console.WriteLine("End second task... {0} seconds", sw.Elapsed.TotalSeconds);
                },
            () =>
                {
                    Stopwatch sw = new Stopwatch();
                    sw.Start();
                    Console.WriteLine("Begin third task...");
                    List<string> objects = new List<string>();
                    for (int i = 0; i < 20000000; i++)
                    {
                        objects.Add("abc" + i);
                    }
                    sw.Stop();
                    Console.WriteLine("End third task... {0} seconds", sw.Elapsed.TotalSeconds);
                }
            );
    }
Run Code Online (Sandbox Code Playgroud)

ParallelOptions可以轻松启用/禁用线程.

当我禁用线程时,我得到以下输出:

Begin first task...
End first task... 10.034647 seconds
Begin second task...
End second task... 3.5326487 seconds
Begin third task...
End third task... 6.8715266 seconds
done!
Total elapsed time: 20.4456563 seconds
 Press any key to continue . . .
Run Code Online (Sandbox Code Playgroud)

当我通过将MaxDegreeOfParallelism设置为-1来启用线程时,我得到:

Begin third task...
Begin first task...
Begin second task...
End second task... 5.9112167 seconds
End third task... 13.113622 seconds
End first task... 19.5815043 seconds
done!
Total elapsed time: 19.5884057 seconds
Run Code Online (Sandbox Code Playgroud)

这几乎与顺序处理的速度相同.由于任务1花费的时间最长 - 大约10秒,我预计线程总共花费大约10秒来运行所有3个任务.什么给出了什么? 为什么Parallel.Invoke单独运行我的任务,但并行运行?

顺便说一句,我在一个真正的应用程序中同时执行许多不同的任务(大多数是运行查询)时使用Parallel.Invoke时看到了完全相同的结果.

如果您认为这是我的电脑,请再想一想......它是1年前的8GB RAM,Windows 8.1,Intel Core I7 2.7GHz 8核心CPU.当我在反复运行测试时观看性能时,我的电脑没有超载.我的电脑从未超出,但显然在运行时显示cpu和内存增加.

J..*_*... 5

我没有对此进行过分析,但这里的大部分时间可能都花在为这些列表和小字符串进行内存分配上.除了以最少的输入和几乎没有处理时间来增加列表之外,这些"任务"实际上并没有做任何事情.

考虑一下:

objects.Add("abc" + i);
Run Code Online (Sandbox Code Playgroud)

基本上只是创建一个新的字符串,然后将其添加到列表中.像这样创建一个小字符串主要只是一个内存分配练习,因为字符串存储在堆上.此外,为其分配的内存将List快速填满 - 每次执行时,列表将为其自己的存储重新分配更多内存.

现在,堆分配在进程内被序列化 - 一个进程内的四个线程无法同时分配内存.由于共享堆就像需要保护免受并发混乱的任何其他共享资源一样,因此按顺序处理内存分配请求.

所以你拥有的是三个极其需要内存的线程,它们可能会花费大部分时间等待彼此完成获取新内存.

用CPU密集型工作填充这些方法(即:做一些数学等),你会发现结果是非常不同的.经验教训是,并非所有任务都可以有效地并行化,而不是以您可能想到的方式完成所有任务.例如,上面的内容可以通过在自己的进程中运行每个任务来加速 - 例如,使用自己的私有内存空间就不存在内存分配争用.

  • @goku_da_master请注意,.NET中有一些不同的GC(特别是从.NET 4.5开始) - 在服务器操作系统上,.NET可以为不同的线程分配单独的堆,这将并行化分配.虽然集合仍然至少部分是阻塞事件(虽然在那里,一些操作可以完成多线程,因此更多的核心有所帮助). (2认同)