设计并发算法的方法论

我们将提出一个五步骤的方法论来获得某一串行算法的并发版本。
该方法论基于 Intel 公司在其“Threading Methodology: Principles and Practices”文档中给出的方法论。

起点:算法的一个串行版本

我们实现并发算法的起点是该算法的一个串行版本。
当然,也可以从头开始设计一个并发算法。

算法的串行版本有两个方面的好处:

第 1 步:分析

在这一步中,我们将分析算法的串行版本来寻找它的代码中有哪些部分可以以并行方式执行。
我们应该特别关注那些执行过程花费时间最多或者执行代码较多的部分,因为实现这些部分的并发版本将能获得较大的性能改进。

对这一过程而言,比较好的候选方案就是循环排查,让其中的一个步骤独立于其他步骤,或者让其中某些部分的代码独立于其他部分的代码 。 (例如一个用于初始化某个应用程序的算法,它打开与数据库的连接,加载配置文件,初始化一些对象。所有这些前期任务都是相互独立的)

第 2 步:设计

一旦你知道了要对哪些部分的代码并行处理,就要决定如何对其进行并行化处理了。
代码的变化将影响应用程序的两个主要部分:

你可以采用两种方式来完成这一任务。

另一个必须牢记的要点是解决方案的粒度
实现一个算法的并发版本,其目标在于实现性能的改善,因此你应该使用所有可用的处理器或核。
另一方面,当你采用某种同步机制时,就引入了一些额外的必须执行的指令。
如果你将算法分割成很多小任务(细粒度),实现同步机制所需额外引入的代码就会导致性能下降。
如果你将算法分割成比核数还少的任务(粗粒度),那么就没有充分利用全部资源。
同样,你还要考虑每个线程都必须要做的工作,尤其是当你实现细粒度解决方案时。
如果某个任务的执行时间比其他任务长,那么该任务将决定整个应用程序的执行时间。你需要在这两点之间找到平衡。

第 3 步:实现

使用某种编程语言来实现并发算法了,而且如果必要,还要用到线程库。

第 4 步:测试

在完成实现过程之后,你应该对该并行算法进行测试。
如果你有了算法的串行版本,可以对比这两个版本算法的结果,从而验证并行版本是否正确。

测试和调试一个并行程序的具体实现是非常困难的任务,因为应用程序中不同任务的执行顺序是无法保证的。

第 5 步:调整

对比并行算法和串行算法的吞吐量。 如果结果并未达到预期,那么你必须重新审查该算法,查找造成并行算法性能较差的原因。
也可以测试该算法的不同参数(例如任务的粒度或数量),从而找到最佳配置。

还有其他一些指标可用来评估通过使算法并行处理可能获得的性能改进。
下面给出的是最常见的三个指标。

结论

并非每一个算法都可以进行并行化处理。
例如,如果你要执行一个循环,其每次迭代的结果取决于前一次迭代的结果,那么你就不能对该循环进行并行化处理。
对性能良好的串行版算法实现并行处理,实际上是个糟糕的出发点。

当你实现一个并发应用程序时(从头开始或者基于一个串行算法),必须要考虑下面几点。