11

我的公司目前正在运行一个第三方模拟程序(自然灾害风险建模),该程序从磁盘中提取千兆字节的数据,然后运行几天以产生结果。我很快将被要求将其重写为多线程应用程序,以便它在数小时而不是数天内运行。我预计有大约 6 个月的时间来完成转换,并将单独工作。

我们有一个 24-proc 的盒子来运行它。我将可以访问原始程序的源代码(我认为是用 C++ 编写的),但此时我对它的设计方式知之甚少。

我需要关于如何解决这个问题的建议。我是一位经验丰富的程序员(约 30 年,目前使用 C# 3.5),但没有多处理器/多线程经验。如果合适的话,我愿意并且渴望学习一门新的语言。我正在寻找有关语言、学习资源、书籍、架构指南的建议。等等

要求:Windows 操作系统。具有大量支持和良好学习资源的商业级编译器。不需要花哨的 GUI - 它可能会从配置文件运行并将结果放入 SQL Server 数据库。

编辑:当前的应用程序是 C++,但我几乎肯定不会使用该语言进行重写。我删除了某人添加的 C++ 标签。

4

16 回答 16

17

数值过程模拟通常在单个离散问题网格(例如,地球表面气体和尘埃云)上运行,这通常排除简单的任务耕作或并发方法。这是因为划分为一组表示物理空间区域的处理器的网格不是一组独立的任务。每个子网格边缘的网格单元需要根据存储在其他处理器上的网格单元的值进行更新,这些处理器在逻辑空间上相邻。

高性能计算中,模拟通常使用MPIOpenMP进行并行化。MPI 是一个消息传递库,绑定了多种语言,包括C、C++、FortranPythonC#。OpenMP 是一种用于共享内存多处理的 API。一般来说,MPI 比 OpenMP 更难编码,更具侵入性,但也更灵活。OpenMP 需要在处理器之间共享内存区域,因此不适合许多架构。混合方案也是可能的。

这种类型的编程有其自身的特殊挑战。除了竞争条件死锁活锁和并发编程的所有其他乐趣之外,您还需要考虑处理器网格的拓扑结构——如何选择在物理处理器之间分割逻辑网格。这很重要,因为您的并行加速是处理器之间通信量的函数,它本身是分解网格的总边缘长度的函数。随着您添加更多处理器,此表面积会增加,从而增加通信开销。增加粒度最终会变得令人望而却步。

另一个重要的考虑因素是可以并行化的代码比例。然后,阿姆达尔定律规定了理论上可达到的最大加速。在开始编写任何代码之前,您应该能够估计这一点。

这两个事实将共同限制您可以运行的最大处理器数量。甜蜜点可能比你想象的要低得多。

如果你能掌握的话,我推荐这本书《高性能计算》。特别是关于性能基准测试和调优的章节是无价的。

Lawerence Livermore National Laboratory的介绍是涵盖主要问题的并行计算的优秀在线概述。

于 2009-12-14T19:09:36.740 回答
12

您在多线程项目中的最大问题是线程间可见太多状态 - 编写以不安全方式读取/改变数据的代码太容易了,特别是在缓存一致性、弱一致性内存等问题的多处理器环境中可能会发挥作用。

调试竞态条件显然令人不快。

假设您正在考虑在网络上的多台机器上分配您的工作:也就是说,确定哪些任务可以并行发生,每个任务的输入是什么,每个任务的输出是什么,以及在给定任务开始之前必须完成哪些任务。练习的重点是确保数据对另一个线程可见的每个位置以及产生新线程的每个位置都经过仔细考虑。

一旦这样的初始设计完成,数据的所有权就会有明确的划分,以及所有权的获取/转移的明确点;因此,您将处于一个非常有利的位置,可以安全地利用多线程为您提供的可能性——廉价的共享数据、廉价的同步、无锁的共享数据结构。

于 2009-12-14T17:14:12.573 回答
7

如果您可以将工作负载拆分为不相关的工作块(即,数据集可以按位处理,没有很多数据依赖性),那么我会使用线程池/任务机制。大概任何 C# 都相当于 Java 的 java.util.concurrent。我会从数据中创建工作单元,并将它们包装在一个任务中,然后将这些任务扔到线程池中。

当然,性能在这里可能是必需的。如果您可以保持原始处理代码内核不变,那么您可以从您的 C# 应用程序中调用它。

如果代码有很多数据依赖关系,那么分解成线程任务可能会困难得多,但你可以将它分解成一个动作管道。这意味着线程 1 将数据传递给线程 2,线程 2 将数据传递给线程 3 到 8,线程 8 将数据传递给线程 9,依此类推。

如果代码有很多浮点数学,可能值得考虑用 OpenCL 或 CUDA 重写,并在 GPU 而不是 CPU 上运行它。

于 2009-12-14T17:09:49.290 回答
3

对于一个为期 6 个月的项目,我会说首先开始阅读一本关于该主题的好书肯定是值得的。我建议Joe Duffy 在 Windows 上的并发编程。这是我所知道的关于该主题的最详尽的书,它涵盖了 .NET 和本机 Win32 线程。当我发现这个 gem 时,我已经编写了 10 年的多线程程序,但仍然在几乎每一章中都发现了我不知道的东西。

此外,“自然灾害风险建模”听起来像是很多数学题。也许你应该看看英特尔的 IPP 库:它为许多常见的低级数学和信号处理算法提供了原语。它支持开箱即用的多线程,这可能会使您的任务变得更加容易。

于 2009-12-14T18:50:40.687 回答
3

如果你为它设计项目,有很多技术可以用来处理多线程。

最普遍和普遍的就是“避免共享状态”。只要有可能,在线程之间复制资源,而不是让它们访问同一个共享副本。

如果您自己编写低级同步代码,则必须记住绝对不要做任何假设。编译器和 CPU 都可能重新排序您的代码,从而在读取代码时创建竞争条件或死锁,这些情况似乎都不可能发生。防止这种情况的唯一方法是使用内存屏障。请记住,即使是最简单的操作也可能会出现线程问题。简单的东西++i通常不是原子的,如果多个线程访问i,你会得到不可预知的结果。当然,仅仅因为您已经为变量分配了一个值,这并不能保证新值对其​​他线程是可见的。编译器可能会推迟实际将其写入内存。同样,内存屏障强制它“刷新”所有未决的内存 I/O。

如果我是你,如果可能的话,我会使用比简单的锁/互斥体/监视器/关键部分更高级别的同步模型。有一些CSP库可用于大多数语言和平台,包括 .NET 语言和本机 C++。

这通常会使竞争条件和死锁难以检测和修复,并允许可笑的可扩展性。但是这种范式也有一定的开销,因此每个线程完成的工作可能比使用其他技术时要少。它还要求整个应用程序专门针对这种范式进行结构化(因此改造现有代码很棘手,但由于您是从头开始,所以问题不大——但您仍然不熟悉)

另一种方法可能是Transactional Memory。这更容易适应传统的程序结构,但也有一些限制,而且我不知道有多少生产质量的库用于它(STM.NET 最近发布,可能值得一试。英特尔有一个 C++带有内置于语言中的 STM 扩展的编译器)

但无论您使用哪种方法,您都必须仔细考虑如何将工作拆分为独立的任务,以及如何避免线程之间的串扰。任何时候两个线程访问同一个变量,你都有一个潜在的错误。并且任何时候两个线程访问同一个变量或只是访问同一地址附近的另一个变量(例如,数组中的下一个或前一个元素),数据必须在内核之间交换,迫使它从 CPU 缓存刷新到内存,然后读入另一个核心的缓存。这可能会对性能造成重大影响。

哦,如果您确实使用 C++ 编写应用程序,请不要低估该语言。您必须先详细学习该语言,然后才能编写健壮的代码,更不用说健壮的线程代码了。

于 2009-12-14T18:23:06.417 回答
2

尤其是阅读 Erlang 和“Actor Model”。如果您使所有数据不可变,您将更容易并行化它。

于 2009-12-14T18:12:59.757 回答
2

这里可以给出很多具体的个人建议,而且已经有几个人这样做了。但是,没有人能准确告诉您如何使这一切满足您的特定要求(您甚至还不完全了解自己),所以我强烈建议您现在阅读HPC(高性能计算)以获得总体概念清晰,并更好地了解哪个方向最适合您的需求。

于 2009-12-14T17:19:19.003 回答
2

在这种情况下,我们所做的一件对我们非常有效的事情是将要完成的工作分解为单独的块,并将每个块上的操作分解到不同的处理器中。然后我们有处理器链,数据块可以独立地通过这些链工作。链中的每组处理器都可以在多个线程上运行,并且可以根据自己相对于链中其他处理器的性能来处理更多或更少的数据。

还将数据和操作分解成更小的部分,使应用程序更易于维护和测试。

于 2009-12-14T17:13:45.723 回答
2

您选择使用的模型将取决于您的数据结构。您的数据是紧耦合还是松耦合?如果您的模拟数据是紧密耦合的,那么您将需要查看 OpenMP 或 MPI(并行计算)。如果您的数据是松散耦合的,那么作业池可能更适合......甚至分布式计算方法也可以工作。

我的建议是获取并阅读介绍性文本,以熟悉各种并发/并行模型。然后查看您的应用程序的需求并决定您将需要使用哪种架构。在您知道您需要哪种架构之后,您可以查看工具来帮助您。

作为该主题介绍的一本相当高评价的书是“并发的艺术:编写并行应用程序的线程猴子指南”。

于 2009-12-14T21:30:50.083 回答
1

大多数其他答案都提供了有关划分项目的好建议 - 寻找可以并行执行且只需要很少数据共享的任务。请注意非线程安全的构造,例如静态或全局变量,或非线程安全的库。我们遇到的最糟糕的是TNT库,它在某些情况下甚至不允许线程安全读取。

与所有优化一样,首先要关注瓶颈,因为线程增加了很多复杂性,你想在不必要的地方避免它。

您需要很好地掌握各种线程原语(互斥体、信号量、临界区、条件等)以及它们在哪些情况下有用。

如果您打算继续使用 C++,我要补充的一件事是,我们使用boost .thread 库取得了很大的成功。它提供了大部分所需的多线程原语,尽管确实缺少线程池(我会警惕可以通过谷歌找到的非官方“增强”线程池,因为它存在许多死锁问题)。

于 2009-12-14T18:13:01.143 回答
1

抱歉,我只想在这里添加一个悲观或更现实的答案。

你面临时间压力。6 个月的最后期限,你甚至不知道这个系统是什么语言,它做什么以及它是如何组织的。如果这不是一个微不足道的计算,那么这是一个非常糟糕的开始。

最重要的是:你说你以前从未做过多线程编程。这是我一次响起4个闹钟的地方。多线程是困难的,当你想把它做好时需要很长时间来学习它——当你想赢得巨大的速度提升时,你需要把它做对。即使使用像 Total Views 调试器或 Intels VTune 这样的好工具,调试也非常令人讨厌。

然后你说你想用另一种语言重写应用程序——这并不像你必须重写它那么糟糕。无需重新设计即可将单线程程序转变为运行良好的多线程程序的机会几乎为零。

但是学习多线程和一门新语言(你的 C++ 技能是什么?),时间线为 3 个月(你必须编写一个扔掉的原型——所以我将时间跨度分成两半)是极具挑战性的。

我的建议很简单,不会喜欢它:现在学习多线程——因为它是未来必备的技能——但是把这份工作留给已经有经验的人。好吧,除非您不关心该计划是否成功并且只是在寻找 6 个月的付款。

于 2009-12-18T08:45:34.397 回答
1

我会考虑在 .NET 4.0 中这样做,因为它有很多新的支持,专门针对使编写并发代码更容易。它的正式发布日期是 2010 年 3 月 22 日,但在此之前它可能会 RTM,您现在可以从相当稳定的 Beta 2 开始。

您可以使用您更熟悉的 C#,也可以使用托管 C++。

在高层次上,尝试将程序分解为System.Threading.Tasks.Task,它们是单独的工作单元。此外,我会尽量减少共享状态的使用,并尽可能考虑使用Parallel.For(或ForEach)和/或PLINQ

如果你这样做,很多繁重的工作将以一种非常有效的方式为你完成。这是微软将越来越多地支持的方向。

2:我会考虑在 .NET 4.0 中执行此操作,因为它有很多新的支持,专门针对简化并发代码的编写。它的正式发布日期是 2010 年 3 月 22 日,但在此之前它可能会 RTM,您现在可以从相当稳定的 Beta 2 开始。在高层次上,尝试将程序分解为System.Threading.Tasks.Task,它们是单独的工作单元。此外,我会尽量减少共享状态的使用,并尽可能考虑使用 Parallel.For 和/或 PLINQ。如果你这样做,很多繁重的工作将以一种非常有效的方式为你完成。 1:http: //msdn.microsoft.com/en-us/library/dd321424%28VS.100%29.aspx

于 2009-12-14T18:32:15.427 回答
0

我不知道它是否被提及,但如果我站在你的立场上,我现在要做的(除了阅读这里发布的每个答案)是用你最喜欢的(最常用的)语言编写一个多线程示例应用程序.

我没有丰富的多线程经验。我过去玩过它是为了好玩,但我认为获得一些使用一次性应用程序的经验将适合您未来的努力。

我祝你在这项工作中好运,我必须承认我希望我有机会从事这样的工作......

于 2009-12-14T22:32:42.693 回答
0

您已将此问题标记为 C++,但提到您目前是 C# 开发人员,所以我不确定您是否会从 C++ 或 C# 处理此任务。无论如何,如果您要使用 C# 或 .NET(包括 C++/CLI):我已将以下 MSDN 文章添加为书签,强烈建议您在准备工作中通读它。

异步调用同步方法

于 2009-12-14T18:20:12.383 回答
0

无论您要编写什么技术,请看一本关于并发的必读书籍“Java 中的并发编程”和 .Net,我强烈推荐用于并发应用程序的retlang 库。

于 2009-12-14T18:28:27.853 回答
0

如果可以让所有线程处理不相交的流程数据集,并将其他信息存储在 SQL 数据库中,那么您可以很容易地在 C++ 中做到这一点,并且只需使用 Windows 生成新线程来处理它们自己的部分API。SQL 服务器将通过其 DB 事务处理所有硬同步魔法!当然,C++ 的执行速度要比 C# 快得多。

您绝对应该为此任务修改 C++,并了解 C++ 代码,并在现有代码中查找效率错误以及添加多线程功能。

于 2009-12-14T17:11:51.313 回答