英文原文:http://tutorials.jenkov.com/java-concurrency/amdahls-law.html
译者有言:本文是原作者 Jakob Jenkov 写的Java并发(Java Concurrency)系列文章中的其中一篇。由于本人没有英文写作和表达能力,所以没有向原文作者申请此文的翻译授权,还请原文作者原谅(虽然他听不到),也请读者们体谅。还有就是本人没有翻译经验,此文是我的第二篇翻译文章,所以肯定有不当之处,欢迎指正。
内容目录
阿姆达尔定律可以用来计算,将一个运算过程的某一部分并行化后,对(这个运算的)速度的提升幅度。阿姆达尔定律的名字是在1967年 Gene Amdahl 提出这个定律之后命名的。许多并行或并发的开发者即使不知道阿姆达尔定律,也能对并行化后可能带来的速度提升有一个准确的预感。尽管如此,阿姆达尔定律仍然值得我们去了解。
我将首先从数学上解释阿姆达尔定律,然后再用图表来解释。
阿姆达尔定律的定义
阿姆达尔定律将一个可被并行化的程序(或算法)分为两个部分:
- 不能被并行化的部分
- 可以被并行化的部分
例如:一个处理硬盘文件的程序,这个程序可能有一小部分是负责扫描目录并创建文件列表内存对象的。然后,每个文件对象被发送给另一个单独的线程进行处理。在这个程序中,负责扫描目录和创建文件列表的那部分不能被并行化,但负责处理文件的部分可以。
我们称,以串行方式(而不是并行方式)执行这个程序时,所耗的时间为T
,T
为可并行化部分和不可并行化部分的时间之和,其中不可并行化部分所耗时间称为B
,而可并行化部分所耗时间则用T – B
表示。以上几个定义归结如下:
T
= 以串行方式执行所耗的时间B
= 不可并行化部分所耗时间T - B
= 可并行化部分所耗时间(以串行而不是并行方式执行时)
由此可得:
T = B + (T - B)
上面这个等式,第一眼看上去也许让人感觉比较奇怪——可并行化部分并没有它自己独有的符号。然而,由于可并行化部分可以用T
和B
组合起来表示,所以在概念上,这个等式反而相对更小了,意思就是说这个等式包含的变量更少了。
T - B
就是所谓的可并行化的部分,它可以通过以并行方式执行来提升速度。而至于到底可以提升多少,则依赖与你执行它时应用了多少个线程或CPU。如果我们把所应用的线程和CPU数用N
表示,那么能得到的最快提升倍数可表示为:
(T - B) / N
另外还有一只表示方式:
(1 / N) * (T - B)
如果你去维基百科[Wikipedia]阅读阿姆达尔定律的词条的话,你会发现它用的就是这种表示方式。
按照阿姆达尔定律,当可并行化部分使用N
个线程或CPU执行时,程序的总体执行时间可由以下公式表示:
T(N) = B + (T - B) / N
其中T(N)
表示以N
个并行因子执行时程序的所耗时间;T
也可以写作T(1)
(可以等同看做以1
个并行因子执行时的所耗时间)。如果使用T(1)
而不是T
表示的话,阿姆达尔定律的形式变为:
T(N) = B + ( T(1) - B ) / N
虽然形式不一样了,但表达的意思却是一样的。
计算示例
为了对阿姆达尔定律有一个更好的理解,让我们来看一个计算示例。假设,一个程序以串行方式执行时所耗时间为1
;其中不可并行化部分占整个程序的40%
,即不可并行化部分所耗时间为0.4
;因此剩下的可并行化部分所耗时间为1-0.4 = 0.6
。
现在以并行化方式执行它,其中并行因子为2
(N=2
,即用2
个线程或CPU来执行可并行化部分),则其所耗时间为:
T(2) = 0.4 + ( 1 - 0.4 ) / 2
= 0.4 + 0.6 / 2
= 0.4 + 0.3
= 0.7
若将并行因子替换成5
,运用同样的计算过程,结果如下:
T(5) = 0.4 + ( 1 - 0.4 ) / 5
= 0.4 + 0.6 / 5
= 0.4 + 0.12
= 0.52
阿姆达尔定律图解
首先,将程序划分为一个不可并行化的部分B
,和一个可并行化的部分1-B
,如下图所示:
其中图片上方标有刻度的线表示程序所耗时间T(1)
。(译注:T(1)
即串行化的时间,可以等同看做并行因子为1
情况)
当其并行因子为2
时的所耗时间为:
并行因子为3
时的所耗时间为:
算法优化
从阿姆达尔定律看,显然,可并行化部分可以通过增加硬件数量来提升其执行速度,而不可并行化部分则只能通过优化算法来提升。也就是说,优化不可并行化部分,也可以提升整体执行速度,甚至可能的话,你可以通过将不可并行化部分中的一部分工作划分到可并行化部分,以使程序的不可并行化部分变得更小。
优化不可并行化部分
同样,在优化不可并行化部分时,也可以应用阿姆达尔定律来计算程序优化后的预期执行时间。如果不可并行化部分B的优化因子为O,则其阿姆达尔定律的计算公式为:
T(O,N) = B / O + (1 - B / O) / N
注意,现在不可并行化部分的所耗时间为B / O
了,相应地可并行化部分的所耗时间应为 1 - B / O
。
如果B = 0.4
,O = 2
,N = 5
,则其所耗时间的计算过程如下:
T(2,5) = 0.4 / 2 + (1 - 0.4 / 2) / 5
= 0.2 + (1 - 0.4 / 2) / 5
= 0.2 + (1 - 0.2) / 5
= 0.2 + 0.8 / 5
= 0.2 + 0.16
= 0.36
执行时间和加速比
目前为止,我们仅仅用阿姆达尔定律计算了一个程序(或算法)在优化或并行化之后的执行所耗时间。但阿姆达尔定律不仅限于此,我们还可以用它计算加速比(speedup),所谓加速比,就是优化后的算法(或程序)相对于旧的算法在速度上的提升倍数。
假设旧的程序(或算法)的执行时间是T,则加速比可表示为:
Speedup = T / T(O,N)
通常我们将T
设为1
,加速比为相对于它的分数形式,则加速比的公式变为:
Speedup = 1 / T(O,N)
如果我们再将T(O,N)
进行代入,则代入后的公式为:
Speedup = 1 / ( B / O + (1 - B / O) / N )
若设 B = 0.4
, O = 2
, N = 5
, 则加速比的计算过程如下:
Speedup = 1 / ( 0.4 / 2 + (1 - 0.4 / 2) / 5)
= 1 / ( 0.2 + (1 - 0.4 / 2) / 5)
= 1 / ( 0.2 + (1 - 0.2) / 5 )
= 1 / ( 0.2 + 0.8 / 5 )
= 1 / ( 0.2 + 0.16 )
= 1 / 0.36
= 2.77777 ...
上面这个计算示例所表示的意思是,如果你用2
个优化因子优化不可并行化部分,用5
个并行化因子优化可并行化部分,则优化后的程序(或算法)将最高得到2.77777
倍的速度提升。
测量,不要只是计算
虽然阿姆达尔定律使你能够通过计算得出一个并行化后的算法的预期速度提升值,但是不要过于依赖这样一种计算。因为在实践中,当你优化或者并行化一个算法时,可能会有许多其他因素产生影响。
例如:内存、CPU缓存,及硬盘、网卡等(如果算法需要与硬盘和网卡交互的话),这些硬件的速度可能也会是一种限制因素。如果并行化一个算法后,引起了大量的“CPU缓存丢失”问题的话,你可能甚至得不到期望的提速效果——“增加N倍的CPU则得到的N倍的速度”;或者,如果优化后的算法最终会导致内存空间、硬盘、网卡、网络连接被占满的话,同样也会得不到预期效果。
所以对于阿姆达尔定律,我的建议是,仅用其来对“优化后的提升速度”有一个大致概念,但是优化后的实际速度提升应通过测量来得出。而且请记住,有时候一个高度串行(单一CPU)化的算法可能胜过并行算法,可能仅仅是因为串行版的算法没有并行算法的协调消耗(分摊工作和汇总结果),而且单一CUP算法更适从底层硬件的工作方式(CPU流水线、 CPU 缓存等) 。