【问题标题】:Parallel.ForEach hangs for a large loopParallel.ForEach 挂起一个大循环
【发布时间】:2016-09-12 04:57:35
【问题描述】:

我有一个使用 TPL 并行化的 for 循环实现。我正在使用具有 4GB RAM 和 i3 Core 处理器的戴尔笔记本电脑。我有多个parallel.foreach 使用Parallel.invoke 调用。该程序是 Enterprise Architect 的插件,用于在 EA 中创建模型图和对象。

代码是这样的:

Parallel.invoke(()=>parent1Creation(),()=>parent2Creation(),...);

每个父创建都是Parallel.foreach:

Parallel.foreach(parents, (parent) => {
    //create parent 
    //create children
    for(child in parent.children) {
        childecreation();
    }

    for(child2 in parent.children) {
        childecreation();
    }
    //can be any type and number of children
} 

我有一个问题,当我的循环大小增加时,即大约 1500-2000 次迭代,Enterprise Architect 停止工作。

这是因为我的笔记本电脑配置或我使用并行循环的方式或与企业架构师一起出现的问题吗?

我该如何解决。

【问题讨论】:

  • “并行化”它的原因是什么?它是 cpu-bound 并且 childecreation() 线程安全吗?
  • 并行化并行任务!不要调用,或者不要并行 foreach。
  • 嗨子创建是线程安全的,父创建也是相互独立的。现在完整的执行需要 12 个小时。由于我的循环有大约 1k 次或更多次迭代,而且子创建也可能是一个大循环。我正在尝试减少创建时间。使用多线程或任何其他方式实现它的最佳方法是什么。
  • 未并行化的代码版本是否也会发生同样的事情?你能调试一下代码,看看 EA 停止工作时它在做什么吗?
  • 与其问为什么您对问题的解决方案(我们不知道)不起作用,不如解释您要解决的问题。那么我们也许可以提出不会遇到同样问题的替代解决方案。

标签: c# multithreading foreach task-parallel-library enterprise-architect


【解决方案1】:

我不建议采用这种策略。一次运行大量 Parallel.ForEach 循环不一定有助于提高性能(请参阅帖子后面的警告),尤其是当每个 Parallel.ForEach 循环都处理大量迭代时。在某些时候,使用额外的线程不会再有利于您的性能,只会增加开销。

这里需要注意的是,Parallel.ForEach 在为特定的 foreach 循环选择最佳线程数方面通常很好(但并不完美)。没有明确保证特定 foreach 循环将使用多少线程(甚至并行运行),因此可以想象多个 Parallel.ForEach 循环实际上会增强您的表现。最好的检查方法是使用调试器查看它在任何给定点实际使用了多少线程。如果这不是您所期望的,您可以检查 Parallel.ForEach 循环中代码的实现(例如);此时您可以采取其他步骤来尝试提高性能(例如,针对 IO 绑定和其他非 CPU 绑定操作的良好异步/等待实现,以便线程可以做更多工作 - 见下文)。

简单示例:假设您有一个系统,其中有 4 个线程和 4 个内核,并且 4 个线程是系统上唯一运行的东西。 (显然这永远不会发生)。从调度的角度来看,明智的做法是让每个核心处理一个线程。假设每个线程一直都很忙(即它永远不会坐在那里等待)如何添加额外的线程来提高你的性能?例如,如果您开始运行 6 个线程,那么显然至少一个内核现在必须运行至少 2 个线程,这会增加额外的开销而没有明显的好处。这里的简化(并且可能是不正确的)假设是您的任务是 100% 受 CPU 限制的,并且线程实际上是在单独的内核上运行的。如果其中一个假设不正确,那么这显然是一个增强的机会。例如,如果一个线程花费大量时间等待 IO 绑定操作的结果,那么 CPU 上的多个线程实际上可以提高性能。您还可以考虑使用 async/await 实现来提高性能。

关键是在某些时候添加额外的线程不会给您带来任何性能优势,只会增加开销(例如,特别是如果所涉及的任务主要受 CPU 限制,而不是主要受 IO 限制)。没有办法绕过这个事实。

非 CPU 绑定操作(例如,IO 绑定任务,例如调用服务器),其中主要阻塞等待来自 CPU/内存外部的结果的并行化方式不同。事实上,async/await 不一定 一定要创建新线程;它的主要行为之一是将控制权返回给相关方法的调用者,并在可能的情况下“尝试”在同一线程上执行其他工作。

重复我最喜欢的类比,假设您作为 10 人小组的一部分出去吃饭。当服务员过来点菜时,服务员要求点的第一个人还没准备好,其他九个人已经准备好了。服务员的正确做法是,与其等第一个人准备好点菜,不如让其他 9 个人先点菜,然后如果第一个人准备好了,再让他点菜。他确实请了第二个服务员来等待那个人准备好。在这种情况下,第二个服务员实际上可能不会减少完成订单所需的总时间。这基本上是 async/await 试图完成的;例如,如果所有操作都在等待来自服务器的结果,那么理想情况下,您可以在它等待的同时做其他事情。

另一方面,为了扩展类比,绝对不是服务员真正自己做饭的情况。在这种情况下,增加更多的人(以此类推,线程)会真正加快速度。

进一步扩展这个类比,如果厨房只有一个四头炉灶,那么在厨房工作人员遇到炉灶大小所施加的硬性限制之前,您可以添加多少人到厨房工作人员中是有硬性限制的.一旦你达到了这个限制,更多的厨房工作人员实际上会减慢速度,因为他们只会互相妨碍,因为一次可以实际烹饪的东西的数量是有硬性限制的。无论你的厨房工作人员有多大,你不可能一次在炉子上烹饪超过 4 种食物。在这种情况下,您拥有的核心数量就像厨房的大小;一旦达到某个点,添加更多厨房人员(线程)会降低您的性能(而不是提高性能)。

【讨论】:

  • 感谢您的解释。您能否建议提高性能的最佳方法。实际上所有这些循环都是相互独立的,可以同时创建。使用顺序循环而不使用 parallel.invoke() 整个过程需要 12 小时。我可以做些什么来减少它,而不需要太多开销。
  • 尽量不要在 Invoke 中调用。在现实世界中,如果您遇到受 CPU 限制且可以并行化的性能问题 - 只做一次,或者根本不做。
  • @Akanksha 你像你一样一次性处理所有父/子元素吗?您可以使用某种仅在需要时进行处理的惰性评估吗?否则,您必须认真重新考虑您的架构。
  • @Akanksha 查看我的编辑,看看它们是否有帮助。 “顺序循环”是指“顺序并行 foreach 循环”(即不使用 Parallel.invoke)还是“使用顺序‘正常’foreach 循环”?
  • @eocron06 哪个更好:使用 parallal.foreach 还是 parallel.invoke?如果我必须选择任何人。
【解决方案2】:

如果您使用 RDBMS 支持的模型,最好对模型执行一些 SQL 以快速完成任务,而不是使用 EA 的 API。

https://leanpub.com/InsideEA 有很多关于结构的细节。

例如使用 SQLServer,使用原始 INSERT 将比遍历 EA 对象快得多,更不用说使用 JOIN 快速获取数据了。

我的脚本使用 SQL 的性能比使用 API 高近 100 倍以上。

不确定 EA COM 对象是否能够如您所愿地被调用。如果是这样,模型更新仍然必须以某种顺序发生,才能正确分配 Object_ID。这可以解释为什么你会在某种锁定限制下运行。

【讨论】:

  • 我不建议您执行 SQL 插入,即使您非常了解数据库结构也不建议这样做。 API 可能有点慢,如果这是关于创建元素(我们不知道,因为 OP 没有解释是问题),那么创建元素的方法非常快。主要技巧是避免循环 EA.Collections,有时会暂时关闭 GUI 反馈。
  • 好吧,用任何语言脚本制作一个 XMI 文件并一次性导入它也是一个相当不错的快速方法。更好的是,导出样本并克隆模板。正如“API 可能有点慢,API 可能像一只很老的狗一样慢”。
  • 我现在大部分时间都在使用针对 EA 模型的链接表来处理一个辅助 Access 项目,并使用它来快速清理/重组/评估大量事物。一个例子是检测错误定向的连接器并以正确的方式交换它们。使用 SQLServer SQL 非常快速和简单。我也有我所谓的“hydradRelationships”视图,它是 t_connector 在开始和结束时与 t_object 连接。允许立即对整个模型进行大量快速查询。使用 SQL 清理杂乱的 t_xref 条目也比使用 API 更容易。
  • @philippeback 感谢,即使我打算有一个基线模板。谢谢。
  • @GeertBellekens 谢谢,我正在尝试关闭 GUI 反馈。使用 SQL 不是一个好主意,我会坚持使用 API,但会尝试从基本模板复制和修改,而不是每次都创建新元素。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2021-05-21
  • 1970-01-01
  • 1970-01-01
  • 2010-11-13
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多