使用Parallel.ForEach和在foreach循环中使用Task的性能差异是什么?

4

我想了解什么是最佳方案,或者是否有任何文件/文章可以帮助我确定在使用普通的for循环时,在Parallel.foreach和Task之间使用的差异,例如以下内容:

案例1 - Parallel.foreach:

Parallel.foreach
{
  // Do SOmething thread safe: parsing an xml and then save 
  // into a DB Server thry respoitory approach
}

第二种情况 - 在foreach循环内部的任务:

foreach
{
  Task t1 = Task.factory.startNew(()=>
  {
     //Do the same thing as case 1 that is thread safe
  }
}
Task.waitall()
  • 我进行了自己的测试,结果显示case 1比case 2表现要好得多。比例大致如下: 顺序 vs case 1 vs case 2 = 5秒:1秒:4秒

虽然在case 1和case 2之间几乎是1:4的比例?所以这是否意味着,如果我们想在循环内并行运行,我们应该始终使用parallel.foreach或parallel.for?


2
小伙子,我对你的测试结果非常不信任……这些数字应该让你警觉起来。 - usr
@Will - 创建任务与创建线程非常不同。这就是TPL的存在理由。 - H H
@ Will,感谢您的建议,我认为我有相同的想法,但为什么结果显示不同?而且是4倍... - mting923
我不明白,您似乎在说对于情况1,4秒比情况2的1秒更好。 - svick
请原谅我的英语不太好,也不太熟悉在这里发布问题的经验。我的意思是,对于每个循环,情况1只需要1秒钟就能完成工作,而情况2则需要大约4秒钟才能完成。【编辑问题以消除混淆】 - mting923
可能是Parallel.ForEach vs Task.Factory.StartNew的重复问题。 - Mohammad
3个回答

1

Parallel.ForEach() 的作用是创建少量的 Task 来处理循环迭代。虽然 Task 相对便宜,但并非免费,因此这通常可以提高性能。如果循环体执行速度很快,则改进可能会非常大。这可能是您观察到的行为的最有可能的解释。


我不确定我理解最后两句话的意思。你所说的“循环体”实现是指什么?是在Parallel.ForEach中还是在ForEach循环内部的任务? - mting923
@mting923 我的意思是循环的一次迭代,而不是整个“任务”。 - svick

1

首先,关于此主题的最佳文档是《CLR via C#》的第五部分。

http://www.amazon.com/CLR-via-C-Developer-Reference/dp/0735667454/ref=sr_1_1?ie=UTF8&qid=1376239791&sr=8-1&keywords=clr+via+c%23

其次,我期望Parallel.Foreach表现更好,因为它不仅会创建任务,还会对它们进行分组。在Jeffrey Richter的书中,他解释说单独启动的任务将被放置在线程池队列上。锁定实际线程池队列存在一些开销。为了解决这个问题,任务本身有自己的任务队列,用于存储它们创建的任务。由任务持有的子队列可以在不锁定的情况下执行一些工作!
我需要再次阅读该章节(第27章),所以我不确定Parallel.Foreach是否是这样工作的,但这是我期望它做到的。
他解释说,锁定是昂贵的,因为它需要访问内核级别结构。
无论哪种情况,都不要期望它们按顺序处理。由于前面提到的内部机制,使用Parallel.Foreach不太可能按顺序处理,而不是使用foreach关键字。

我还没有开始阅读这本书,但我同意你关于parallel.each中“group”功能的观点。在我的parallel.each中,我期望一个块将是关键部分,但实际上当我以parallel.foreach方式实现它时,它并不是。在我完成阅读本章后,我会更多地发表评论。感谢您的推荐,这本书看起来非常有帮助,可以加强我的知识。 - mting923
我认为他在Parallel的上下文中并没有直接谈论Task队列机制,但它可能出现在那一章节或下一章节。 - Phillip Scott Givens

0
你正在运行多少个任务?如果你循环的次数足够多,仅仅创建一个新任务就可能需要大量时间。例如,以下代码在第一个块中运行15毫秒,在第二个块中运行超过1秒,而第二个块甚至没有运行任务。取消注释Start,时间将增加到近3秒。 WaitAll只会增加一点点时间。
static class Program
{
    static void Main()
    {
        const int max = 3000000;
        var range = Enumerable.Range(0, max).ToArray();
        {
            var sw = new Stopwatch();
            sw.Start();
            Parallel.ForEach(range, i => { });
            sw.Stop();
            Console.WriteLine(sw.ElapsedMilliseconds);
        }
        {
            var tasks = new Task[max];
            var sw = new Stopwatch();
            sw.Start();
            foreach (var i in range)
            {
                tasks[i] = new Task(()=> { });
                //tasks[i].Start();
            }
            //Task.WaitAll(tasks);
            sw.Stop();
            Console.WriteLine(sw.ElapsedMilliseconds);
        }
    }
}

感谢提供示例代码,我会在给出反馈之前先尝试一下。但我也想获取每个循环的平均时间以进行比较。 - mting923

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接