通常支持超线程的多核处理器能够使用的线程数最多是物理核心数的2倍
X86 流加载/流存储:
__mm_stream_load
__mm256_stream_load
__mm_stream_store
__mm256_stream_store
SSE中的 prefetch指令 可以实现软件预取技术
提高多路系统中多核处理器之间通信的带宽。
原理:访问存储器的速度与距离处理器的距离有关,为了满足分配的从“近端”起。
#pragma omp parallel
{
int myid = omp_get_thread_num();
data[i] = (float*) malloc(size);
init(data[i]);
}
gprof :通过在编译时插入代码来分析程序
nvprof :NVIDIA开发,用于运行在GPU上CUDA程序性能的工具
vampire Trace :基于命令行的并行程序剖分工具(vampire 图形化显示)
Intel VTune
perf: 跟踪内核调用,支持功耗剖分(软/硬件计数器)
(perf、gprof、valgrind对于串行程序剖分相当有用,但是并不适用于并行)
(vampire 剖分并行程序)
-O3 -ffast-math -funroll-all-loops -mavx -mtune=native
fast-math: 对超越函数使用更快但精度低的版本
unroll-all-loops:使用循环展开
avx: AVX指令集向量化
tune=native 为当前编译的处理器做优化
BLAS 基本线性资程序
FFTW 快速傅里叶变换自由软件库
条件编译生成的代码更短,因此效率更高
C:
#if#else#elif#endif#ifdef
生成的指令更短,对指令缓存的利用更好
缓存优化:
#GCC
__attribute__((aligned(x)))
__attribute__((packed()))
for(int i=0;i<len;i++){
if(i&1==0) do0();
else do1();
}
// 1
for(int i=0;i<len-1;i+=2){
do0();
do1();
}
if(0 != len%2) do0();
// 2
for(int i=0;i<len;i+=2){
do0();
}
for(int i=1;i<len;i+=2){
do1();
}
if(0 != len%2) do0();
合并多个条件(要求在分支执行前计算出分支的结果)
使用条件状态生成掩码来移除条件分支
查表法移除分支
利用短路原来来确定分支顺序
// swap
//优化前
unsigned char tmp = a[ji];
a[ji] = a[jj];
a[jj] = temp;
//优化后
unsigned char aji = a[ji];
unsigned char ajj = a[jj];
a[ji] = ajj;
a[jj] = aji;
//好处:消除了读写之间的相互依赖
//前缀和的优化
for(int i=1;i<len;i++){
a[i]+=a[i-1]
}
//优化
float tmp=0.0f;
for(int i=0;i<len;i++){
tmp+=a[i];
a[i]=tmp;
}
//common use
sum =0;
for(int i=0;i<n;i+=4){
sum += a[i];
sum += a[i+1];
sum += a[i+2];
sum += a[i+3];
}
// 都依赖于sum的刷新(串行)
//优化
sum=sum1=sum2=sum3=0;
for(int i=0;i<n;i+=4){
sum += a[i];
sum1 += a[i+1];
sum2 += a[i+2];
sum3 += a[i+3];
}
sum += sum1;
sum2 += sum3;
sum += sum2;
支持隐式共享内存器并行的环境:OpenMP、OpenACC、SSE/AVX、NEON内置函数
支持显式共享内存器并行的环境:pthread、cilk、CUDA、OpenCL
支持显式消息传递并行的环境:MPI
并行算法设计的基本步骤:划分、通信、结果归并、负载均衡
操作的原子性、结果的可能性、函数的可重入性、顺序一致性
常见的并行程序通信方式:锁、临界区、原子操作、barrier、volatile关键字
静态负载均衡和动态负载均衡
来源:并行算法设计与性能优化(刘文志)