本文将主要介绍并行计算的一些基本概念,以及几种用于并行编程的工具/框架。
早期传统软件为串行计算而设计,在仅有一个处理器的单机上运行;将一个问题分解成离散的串行指令序列求解;指令是一个接一个执行;在任意时间任意时刻,只有一条指令在执行。
而并行计算可以简单定义为同时利用多个计算资源解决一个计算问题。
可并行的计算问题应该满足:
计算资源一般为:
并行程序中的协作:通信、负载均衡、同步
并行计算是现实生活的需要
优势有以下几点
迄今为止,CPU架构技术经历了四代即:电子管(Tube) 、晶体管(Transistor)、集成电路(IC)和大规模集成电路(VLSI),这里只关注VLSI。 VLSI最大的特色是在于对并行化的利用,不同的VLSI时代具有不同的并行粒度:
Moore定律失效(功耗墙),发展趋势不再是高速的CPU主频,而是“多核”
1990年之前的解决方式:
2000年之后的解决方式:
未来属于异构、多核的SOC架构,SOC = System On Chip
Moore定律新解:
从硬件角度来讲,今天的单个计算机都是并行计算机,主要体现为:
多个单独的计算机通过网络连接起来形成计算集群,如LLNL并行计算集群
并行计算不仅仅是硬件,还包括了并行编程
经过30多年的研究:
任务并行:
数据并行:数据密集型,大数据应用
并行计算中最重要的即为Amdahl’s law,用于度量并行程序的加速效果
\[Speedup=\frac{1}{(1-p)+p/n}\]它给出了并行程序的加速比,其中\(p\)是可并行占用的时间比例,\(1-p\)为串行占用的时间比例,\(n\)为线程的数目。
但太过乐观估计
新的加速比公式:\(\sigma\)为串行部分时间,\(\varphi\)为并行部分时间,\(n\)为问题规模,\(p\)为核数,\(\kappa\)为并行开销,如下所示
\[\psi(n,p)\leq\frac{\sigma(n)+\varphi(n)}{\sigma(n)+\varphi(n)/p+\kappa(n,p)}\]按指令(Instruction, I)和数据流(Data, D)的数目划分:SISD、MISD、SIMD、MIMD
计算机架构的目标(2002年以前):将底部设施的并行从OS、编译器、程序员中隐藏
ILP技术
TLP技术,要区别并发和并行
结合线程级并行和指令级并行:同时(simultaneous)多线程/超线程(hyperthreading)
内存带宽的影响:内存带宽由内存总线和内存单元数目决定
现在通用提升cache性能的方法是blocking/tiling:通过divide-and-conquer设计问题使得能够符合寄存器/L1/L2的大小
Single Instruction Multiple Data (SIMD)是提升CPU性能一个很重要的技术,即数据并行—在CPU内部添加向量寄存器
#include <immintrin.h>
程序员写应用时运用的库/API,定义了通信和同步机制,包括
编程模型需要包括以下几个部分:
并行程序可能出现的问题
线程不安全的例子
最好的方式是局部更新变量/互斥锁,或者使例程可重入
1980s已经有了消息传递库,但不统一/兼容性弱。 消息传递界面(MPI)第一版发布于1994年,MPI-2发布于1996年
增量式并行化通过研究一个串行程序,尝试找到可以并行的地方和可能存在的瓶颈,并尝试令所有的处理器保持做有用的工作。
三种基本并行类型:数据并行、任务并行、流水线,其实都可以被整合
在POSIX(Portable Opearing System Interface for Unix)中,pthreads是关于线程的界面,用于创建同步线程 关于C/C++下pthreads的使用,可见C/C++多线程。
注意:创建线程的开销依然很大,需要10k个量级的时钟周期,因此尽可能少创建线程,最好线程数等于核数。
从Intel C++ Compiler 18.0开始Cilk Plus就被废除了,而交由MIT自己维护。 转而取代的是Intel Threading Building Blocks (TBB)
task_group t; t.run([](){ })
t.wait()
tbb::parallel_for()
一个迭代的过程,不断分解、重排、协调,目标是:
先实施一个最简单的方式,测试性能决定你是否需要做得更好(比如小机器/核数限制就没有必要用大并行)
两种指派方法:分块(blocked)、交替(interleaved)
实现负载均衡的方式
关键在于选择合适的任务粒度(granularity):粒度小容易负载均衡,但同步开销大,临界区串行部分多;粒度大减少同步开销。 通常通过负载(workload)和机器的特性决定粒度大小。
解决负载不均的方法:
实际上只要是内存访问都可以称之为通信,包括
C[i] = A[i] + B[i]
运算密度为1/3(两读一存一算)Cache的四个C
提高cache局部性的方式
冲突(contention):在邻近时间内对同个资源的大量请求(hotspot),如更新一个共享变量(树结构可减少冲突,但带来更大的latency)
减少通信开销的方法:
程序优化
程序debug
进阶分析