Parallel.Foreach和普通的ForEach一样快/慢吗?(提问一个并行处理相关的问题)

13

更新:我使用线程将循环分割成内核数量(我的情况下为8),整个循环在不到1秒的时间内完成。 因此,问题不在于使用线程操作速度没有提高。为什么并行扩展在这种情况下失败了?

大家好。我想使用Parrallel.Foreach来转换我的ForEach。问题是,并行化对我几乎没有任何优势。

foreach (Entities.Buchung buchung in buchungen) {
    Int32 categoryID = manager.GetCategoryID(new Regelengine.Booking(buchung)); // Average 4ms
    buchung.Category = categoryID.ToString();
}

并行:

System.Threading.Tasks.Parallel.ForEach(buchungen, buchung => {
    Int32 categoryID = manager.GetCategoryID(new Regelengine.Booking(buchung));
    buchung.Category = categoryID.ToString();
});

结果:

---------------------------
Stopwatched Results for 1550 entries in the List:
---------------------------
Parallel.Foreach 00:00:07.6599066
Average Foreach: 00:00:07.9791303

也许问题在于循环中实际操作非常短?但没有人能告诉我,在Intel I7上并行执行1550个操作不会节省任何时间。


那个 Regelengine 里可能有一个锁。 - leppie
2
问题是:语句中的方法是否从并行性中获益?我不知道的下一件事是,GetCategoryID是做什么的。是否存在数据库调用可能成为瓶颈,并阻止代码使用多线程。 - Alexander Schmidt
2
方法 manager.GetCategoryID 中发生了什么?构造函数 new Regelengine.Booking 中发生了什么? - AakashM
没有数据库或网络调用。构造函数将我的实体转换为用于调用运行在.COM库上的manager.GetcategoryID()的实体。 - Steav
5个回答

9
使用Parallel.For只能利用一个资源:CPU周期。当您拥有N个核心时,理论上可以将代码的速度提升N倍。但是这需要在代码中CPU周期是瓶颈的情况下才能实现,而通常情况下除非执行计算密集型代码,否则不会是这种情况。其他限制因素包括硬盘速度、网络连接、数据库服务器,在某些情况下还包括内存总线的带宽。Parallel.For并不能魔法般地为您提供另一个硬盘。
测试Parallel.For是否能加速您的代码非常简单。只需在未并行化的情况下运行代码,并观察Taskmgr.exe或Perfmon中的CPU负载即可。如果一个核心没有达到100%的运行速度,则您的代码不是计算受限的。如果它以10%的速度运行,则无论您有多少个核心,您都只能希望将其时间缩短为90%。您可以通过将I/O等待时间与处理时间重叠,使用两个线程来完成这项任务。

5

在这方面,您应该考虑以下问题:

  • 启动线程的开销是多少?
  • 我的线程安全(锁)的开销是多少?
  • 实际瓶颈在哪里,多线程是否真的有帮助?

最后一个问题是您在这里需要考虑的最重要的问题。例如,如果您的I/O通道已经达到最大值,那么所有线程都无济于事。因此,您的任务是CPU绑定还是I/O绑定?


线程的创建不应该昂贵到会产生这种效果。而说锁与说在哪里使用并没有什么用处。 - Jonathan Allen
谢谢您的回答,但是我现在使用普通线程手动拆分循环,效率非常高。因此问题不在于线程本身,而在于ForeachLoop? - Steav

1

我认为你是对的,看起来使用并行foreach可能有点太短了。我只在知道foreach中会发生一些重要的事情需要时间或可能需要时间时才使用并行foreach,比如数据库连接或者向Web服务发送大量数据。如果只是在服务器上处理信息,比如从已经加载到内存中的集合中获取ID,则真的不值得这样做。


1

如果没有可用的核心来使用,那么并行处理不会更快。所以当我看到像这样的代码时,我的第一个想法是你有其他线程在运行。

也可能是工作负载的问题。同步逻辑不是免费的,每次迭代也没有做太多事情。考虑查看其他 Parallel.ForEach 的重载选项,看看你可以调整哪些选项。

还可以尝试使用 Parallel.For。你无法以并行方式从 IEnumerable 中读取,但可以使用索引从 IList 中读取。


0
首先,1550并不多。例如,对这么多元素的数组进行排序通常在顺序执行时比并行执行更快。这一切都取决于操作。
其次,GetCategoryID是做什么的?它使用锁吗?同样,Regelengine.Booking构造函数呢?
总运行时间为7秒表明该操作足够缓慢,应受益于并行化。另一方面,您的代码似乎表明实际上没有进行太多处理。您最可能从磁盘或数据库加载数据。在这两种情况下,并行化几乎无法解决瓶颈问题。只有当处于计算绑定状态时,并发处理才能使您的代码更快。
但是你没有提供足够的信息来确定这一点。

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