第1章 引言
在多核体系环境中,CPU仪表盘上经常显示着一个问题,即计算机上只有一个内核在满负荷运行,而其他内核则都被闲置了,这使得我们的程序看上去似乎受到了CPU性能的限制,但事实上,这才仅仅利用了多核系统中的一小部分性能而已。那么,这个问题有更好的解决方案吗?
答案是简单而明确的,那就是并行编程(www.cppentry.com)(parallel programming)。现如今,我们越来越多地发现,自己曾一直习惯于写的并且也为所有程序员熟悉的那种串行化代码(sequential code)显然已经无法满足用户的性能需求了。为了进一步提升系统中CPU资源的使用率,我们需要将应用程序分解成能各自为战的执行片断(piece),以便使它们能够在同一时间内运行。
并行编程(www.cppentry.com)使我们可以同一时间内调用多个内核资源,这能有效地提升应用程序的运行速度。
当然,说起来容易做起来难。毕竟并行编程(www.cppentry.com)一直被认为是专家们的领域,同时也常被视为是一个雷区,其中隐藏着各种难以重现的、诡异的软件缺陷。几乎每个程序员都会跟你老生常谈一个关于神秘bug如何导致并行程序运行结果与期望大相径庭的故事。
也许,那些故事让你对自己所面临的困难有了一些清醒的认识。但幸运的是,你不是一个人在战斗,并行模式库(Parallel Patterns Library,PPL)和异步代理库(Asynchronous Agents Library)引入了一个全新的并行编程(www.cppentry.com)模型,该模型大大地简化了编写并行程序的工作。当然,这些简化的背后隐藏着一系列精致而复杂的算法,它们能够很好地适应于多核体系(multicore architecture)中的动态分布式计算。 此外,Visual Studio2010开发系统中还内置了一系列的调试器与分析工具,以便支持这个全新的并行编程(www.cppentry.com)模型。
如果觉得这里的内容太过拖沓,你也可以根据自己的需要直接参考1.3节。
另外,你还可以求助于久经考验的设计模式(design pattern),在本书中,我们介绍了一些最重要的、也是最常用的并行编程(www.cppentry.com)模式,并提供相应的基于PPL编写的可执行代码示例,对其进行辅助说明。至于这本书的使用问题,我们建议你最好先大致浏览一遍接下来的章节中提到的6个模式,看看你的问题中是否有一些特征与这些模式是相匹配的。如果答案是肯定的,就说明这些模式(包括相应的代码示例)是值得你去深入地学习、研究的。
尽管编写并行程序确实是出了名的难,但你不是一个人在战斗。
由于大部分并行程序都应该或多或少地遵守这些设计模式,因此,我们通常很容易就能找到一个相匹配的模式来解决特定的问题。如果真的没有一个用武之地,那很可能是因为我们遇上了一个难度很大的问题,这时候我们或许就需要去寻求专家或者专业文献的帮助了。
在本书中,所有的样例代码都可以在下面的网站下载:http://parallelpatternscpp. codeplex.com/。
1.1 潜在并行化的重要意义
本书中提到的所有模式都旨在帮助我们发掘出程序中潜在的并行化。所谓的潜在并行性(potential parallelism),实质上是指,如果应用程序得到了并行硬件的支持,它就能运行得更快;即使硬件不支持并行化,它的性能也理应与其等价的串行程序相差无几。只要代码的架构设计正确,运行时环境就应该能根据计算机上具体的工作负载做出自动调整。当然,本书中的这些模式也只能对潜在并行化提供一般性的提示,毕竟谁也无法保证程序在任何情况下都能实现并行执行。但是,挖掘这种潜在并行化正是PPL编程(www.cppentry.com)模型的核心思想所在,值得我们为此进一步说明。
这里说明了程序中的潜在并行化无论在多核还是单核的情况下,都可以让处理器中的所有内核保持有效负荷。
在常见的并行应用程序中,有不少是为特定的硬件而编写的。以游戏控制平台为例,游戏程序员可能事先就对程序在运行时可用的硬件资源了如指掌,包括处理器内核数量和内存架构等细节。当然,对于硬件平台的全面掌握也是嵌入式应用程序开发的基本特征,例如工程进度控制(industrial process control)程序就是这样。不过,这也决定了这些程序的生命周期必须要和它们所依赖的硬件保持一致。注1
相反,如果是在桌面工作站或者服务器这样的通用计算平台上编写程序,我们对硬件特性的估测能力就要打些折扣了。毕竟,我们不可能总能知道有多少内核可供使用,也不太可能预先知道会有什么其他软件将与程序共同运行。
不要对应用程序的并行度进行硬编码(hard code),因为运行时可用的内核数量往往是无法预知的。
即便应用程序最初的运行环境是确定的,随着时间推移,它也是会发生变化的。在过去,程序员总是假定自己的程序在下一代硬件中会运行得更快。你现在依然可以这样认为,因为处理器主频终究还是会越来越快的。不过,对于现在的多核处理器而言,主频增长的步伐已经开始慢了下来,取而代之的是处理器中的内核越来越多。如果想让我们的应用程序在多核世界中分享到硬件进步所带来的利益,就必须要对程序设计模型做出相应地调整,以便程序在今后能运行在多核计算机上。同时,我们还应该致力于发掘程序中潜在的并行计算能力,以便体现出某种“前瞻性”。
处理器的发展趋势不再是主频速度越来越快,取而代之的是增加越来越多的内核。
最后,我们还有必要为可能的意外情况做一些准备,毕竟,总会有些用户无法获得最新的硬件,这时候,我们需要确保并行程序在单核计算机上的性能至少和只用串行代码编写的程序相差无几,换句话说,就是要让应用程序在单核和多核之间保持性能的可扩展性,让其拥有更强的硬件适应能力,并保持前瞻性。而这正是发掘潜在并行化的目的所在。
一个设计良好的并行程序在单核情况下的效率应该与相应的串行程序相差无几。
例如,我们将在第2章中介绍并行循环模式就是一个发掘潜在并行化的典型示例。对于一个将要迭代100万次的for循环来说,如果能确定其中的每个迭代体都是彼此独立,我们就完全有理由将这些迭代分解为并行计算,把工作分配到各个闲置的处理器内核中。显然,具体的分配工作取决于可用内核的数量,多数情况下,该循环的速度将与内核数成正比。