困难的分类:
目前这些问题的现状:
大多并行编程的尝试都是为了提升性能。摩尔定律仍然在晶体管密度方面有效,但在单线程性能方面已经不再有效,因此 对性能的关注点从硬件转移到了并行软件。这意味着先编写单线程代码,再通过升级 CPU 来提升性能已不再可行。因此首先要考虑的是性能而不是扩展性。
即使拥有多个 CPU,也不必全部都用起来。并行编程主要是为了性能优化,但这只是众多优化措施中的一种。 如果程序够快则无需优化或并行化,亦或是基于单线程方式的优化。如果需要基于并行的方式进行优化,则需要与最好的串行算法进行比较,已确定并行化的必要性。
硬件的价格已远低于软件的开发与维护成本。仅仅高效的使用硬件已经不再足够,高效的利用开发者已经变得同等重要。
要想减少开发并行程序的高昂成本,一种方式是 让程序尽量通用。如果其他影响因素一样,通用的软件能获得更多用户从而摊薄成本。但通用性会带来更大的性能损失和生产率损失。如下是一些典型的并行开发环境。
同时满足性能、生产率、通用性要求的并行编程环境仍不存在,因此必须在三者之间进行权衡。 越往上层,生成率越重要;越往下层,性能和通用性越重要。大量的开发工作消耗在上层,必须提高通用性以降低成本;下层的性能损失很难在上层得到恢复。越往上层,采用额外的硬件比采用额外的开发者更划算。
本书面向底层开发,因此主要关心 性能和通用性。
并行编程只是提升性能的方案之一,以下是一些其他流行的方案:
并行编程需要双向交互:人告诉计算机要做什么;人还需要通过结果的性能和扩展性来评价程序。
我们所要考虑的并行程序开发者的任务,对于串行程序开发者来说是完全不需要的。我们将这些任务分为 4 类:
合理的对任务进行分割以提升并行度,可以极大提升性能和扩展性,但是也会增加复杂性。 比如,分割任务可能会让全局错误处理和事件处理更复杂,并行程序可能需要一些相当复杂的同步措施来安全的处理这些全局事件。总的来说,每个任务分割都需要一些交互,如果某个线程不存在任何交互,那它的执行对任务本身也就不产生任何影响。但是交互也就意味着额外的、可能降低性能的开销。
如果同时执行太多线程,CPU 缓存将会溢出,引起过高的缓存未命中,降低性能。
运行程序并发执行会大量增加程序的状态集,导致程序难以理解和调试,降低生产率。
单线程的线性程序对程序的所有资源都有访问权,主要是内存数据结构,但也可能是 CPU、内存、高速缓存、IO 设备、计算加速器、文件等。并行访问控制的问题有:
最有效的并行算法和系统都善于对资源进行并行化,所以并行编程的编写最好从分割写密集型资源和复制经常访问的读密集型资源开始。 这里所说的访问频繁的数据,可能是计算机系统、海量存储设备、NUMA节点、CPU、页面、Cache Line、同步原语实例、代码临界区等等多个层次。
开发者需要根据目标硬件的高速缓存分布、系统的拓扑结果或者内部互联协议来对应用进行量体裁衣。
最好的实践会将上述 4 种类型的基础性任务组合应用。比如,数据并行方案首先把数据分割以减少组件内的交互需求,然后分割相应的代码,最后对数据分区并与线程映射,以便提升吞吐、减少线程内交互。