. NET4.0并行计算技术基础(2)
上一部分 介绍了CPU与“核”以及“并行”和“并发”的区别,这一部分我们将进一步介绍并行计算的性能衡量与并行计算系统的大致分类,为后面介绍.NET 4.0的并行计算打下基础。
3 如何衡量并行计算的性能提升?
之所以要研究并行计算,其目的是获得更好的性能。一个软件系统的性能,通常使用两个指标来进行衡量:
(1) 响应速度( Responsiveness ) :用户向软件系统提交一个工作任务,软件系统要花费多长的时间才能处理完毕并将结果通知用户?
(2) 吞吐率( Throughput ) :在单位时间间隔内软件系统最多可以处理多少个工作任务?
并行计算的优势就在于它可以缩短系统完成单个工作任务的时间和提升系统的吞吐率。
那么,我们怎样定量衡量因为“并行”而带来的系统性能提升?很明显,这里所说的“提升”是有参照物的,对于并行算法,最直观的参照物就是完成同样功能的串行算法。
人们使用 “加速系数( speedup factory ) [1] ”来定量衡量一个并行算法的好坏:
加速系数越大,说明性能提升越明显。
我们感兴趣的是:
在 p 个处理器上执行并行算法,到底比在单个处理器上执行串行算法理论上能快多少?如果增加处理器的个数,加速系数能否进一步增大?这种增大有没有一个限制?
针对这个问题,并行计算理论中有一个著名的“阿姆达尔定律( Amdahl’s law )”回答了这个问题。
鉴于在实际中“ 100% 纯粹”的并行算法很少,此定律假设算法应同时包容串行执行和并行执行两部分,其中,串行执行的部分占整个算法的比例为 f ,并且假设并行执行部分除了计算外并无其他开销,则在 p 个处理器上执行此算法,其加速系数为:
从这个公式可以得到一个有趣的结论。试着让 p à ∞ ,则上述公式变为:
这说明:在算法中串行部分的比例不变的情况下,不算你使用多少个处理器,加速系数最大也超不过 1/f !
例如,假设某算法中有 20% 的串行算法,则不管后面你如何加速,最大加速系数超不过 5 ( 1/0.2=5 )。
实际情况可能更糟,因为使用并行算法必然会有其它一开销,比如并行算法都是使用线程来执行的,因此,线程的上下文切换就必须耗用系统资源,考虎到这一点,修正后的“阿姆达尔定律”变为:
其中 H(p) 代表算法并行部分在单个处理器上执行所带来的系统开销,不同的算法 H(p) 的值也不一样。
这说明,如果系统开销非常大,那么采用多线程并行执行一个算法,不但没有加速,反而会降低了程序的执行性能。
依据“阿姆达尔定律”得出的这个结论令人沮丧!但由此也可以得到一个重要的结论: 如果希望使用并行计算来提升程序的性能,那么应尽可能地减少程序中串行代码的比例。
[1] 有的书中将其称为“加速比”。
扩充阅读:
“乐观”的 Gustafson 定律
“阿姆达尔定律”的结论让人沮丧,但到了 20 世纪 80 年代晚期, Sandia 国家实验室的科学家们在对具有 1024 个处理器的超立方体结构上观察到了 3 个实际应用程序随着处理器的增加发生线性加速的现象,科学家 John L. Gustafson 基于此实验数据在 1988 年提出了一个新的计算加速系数的公式:
与“阿姆达尔定律”一样, S(p) 代表加速系统, p 代表处理器数量, f 代表算法中串行部分所占的比例。
Gustafson 定律说明在许多实际的应用程序中得到接近线性的加速效果是可能的。
“阿姆达尔定律”的问题出在它的前提过于理想化。因为并行算法通常能处理比串行算法更大规模的问题,即使算法仍然存在着串行部分,但由于问题规模的不断扩大,往往会导致算法中串行部分所占比例的持续减少。 Gustafson 定律又点燃了人们继续研制集成更多处理器(或集成更多执行核的处理器)的计算机系统的热情。可以预测,未来的计算机将集成更多的处理器,当前双核个人电脑的普及只不过是一个开始罢了。
4 实际测量并行程序的性能
不管是“悲观”的阿姆达尔定律还是“乐观”的 Gustafson 定律都只是一种理论上的结果,在真实的运行环境中,影响程序性能的因素有许多,仅靠几个简单的公式并不能真正计算出一个真实的“并行”程序比相应的“串行”版本到底快了多少。最可靠的方法还是实地测试,在同样的软件和硬件环境中,先后运行程序的“并行”和“串行”两个版本,比对它们的执行时间,就知道结果了。
在 .NET 程序中,我们可以使用 StopWatch 类来测试算法的性能。
以下是使用 StopWatch 测试算法运行时间的代码框架:
Stopwatch sw = new Stopwatch();
sw.Start(); // 启动计时
// 编写代码执行被测试的算法
sw.Stop();// 停止计时
// 获取算法执行时间
long UsedTime = sw.ElapsedMilliseconds;
//...
另外,调用 StopWatch 对象的 Reset() 方法可以重置计时值,再接着调用 Start() 方法就可以开始一个新的计时。
5 并行计算系统的大致分类
有两种主要的并行计算系统类型。一种称为“ 共享存储器( Shared Memory )的多处理器 ”系统,其思想很简单,所有处理器都可以访问同一个主存储器,从而允许运行在多个处理器上的各个线程可以通过主存储器方便地共享信息。
基于共享存储器的并行计算系统发展已有许多年,人们也为开发运行于这种平台的软件提供了不少工具,其中得到广泛应用的是 OpenMP ,它可以帮助程序员使用 Fortran (或 C/C++ )语言高效地开发多线程并行计算程序。
C# 和 Visual Basic 编译器没有遵循 OpenMP 标准,但通过给 .NET 基类库添加并行扩展( Parallel Extensions ),可以使用比 OpenMP 更方便的方法设计并行计算程序。
扩充阅读:
OpenMP 简介
针对共享存储器的并行计算系统,为了简化软件开发工作,人们设计了一个 OpenMP 标准,通过给代码中添加特定的编译指令,让实现了此标准编译器自动地帮助我们生成并行指令代码。例如,以下是一个典型的循环代码:
for (int i = 0; i < 100000; i++)
{
// ……
}
给上述代码添加一个特殊的编译指令 :
#pragma omp parallel for
for (int i = 0; i < 100000; i++)
{
// ……
}
遵循 OpenMP 标准开发的编译器 “认识”此指令,在编译时会自动生成相关的指令让此代码可以并行执行。 OpenMP 的实现机制决定要创建的线程个数,以及如何能够尽善尽美地管理这些线程。开发人员只需告诉 OpenMP 哪个循环应该以多线程方式执行,至于那些为了利用并行性而进行的线程创建、初始化、线程同步等工作都可以交给 OpenMP 编译器来完成。
OpenMP 的标准形成于 1997 年,支持使用 Fortran 、 C/C++ 语言编写的程序。
另一种称为“消息传递的多处理机”系统,其特点是往往由多台相互连接的计算机构成一个并行计算平台,计算机之间通过“消息”的传递协同工作。
基于消息传递构造的并行计算系统本质上是一个分布式软件系统,由于互联网的无孔不入,利用互联的计算机实现并行计算是当前一个非常活跃的领域。在微软平台之上,使用 WCF 可以很方便地开发这种类型的软件系统。本书第 9 篇详细介绍 WCF 。
=====================
请看下一讲 《.NET 4.0并行计算技术基础(3)》