MIC优化方法:
--并行度优化
--内存管理优化
--数据传输优化
--存储器访问优化
--向量化优化
--负载均衡优化
--MIC线程扩展性优化
一:并行度优化
要保证有足够的并行度,效果才能好(数据并行,任务并行)
优化步骤:
1.写OpenMP程序
2.测试他的扩展性,比如用两个测试,然后4个,6个,8个线程测试
3.然后移植到MIC上面
MIC优化准则:外层并行,内层向量化
示例一:
for(i=0;i<M;i++)
{
for(j=0;j<N;j++){
......
}
}
两种并行模式:
其一:
#pragma omp parallel for num_threads(THREAD_NUM) //开启了M次
for(i=0;i<M;i++){
for(j=0;j<N;j++)
{......}
}
其二:
#pragma omp parallel num_threads(THREAD_NUM)
for(i=0;i<M;i++){
#pragma omp for //开启了M*N次,开销较大
for(j=0;j<N;j++)
{......}
}
示例二(嵌套并行):
for(i=0;i<M;i++)
{
for(j=0;j<N;j++) //问题:每层循环次数太少,不能很好的发挥MIC卡的作用
{......}
}
可以改变成如下两种形式:
其一:
#pragma omp parallel for num_threads(THREAD_NUM)
for(k=0;k<M*N;k++){
i=k/M;
j=k%M;
}
其二:
omp_set_nested(true); //声明允许嵌套并行
#pragma omp parallel for num_threads(THREAD_NUM1)
for(i=0;i<M;i++){
#pragma omp parallel for num_threads(THREAD_NUM2)
for(j=0;j<N;j++){......}
}
二:内存管理优化
MIC内存大小:6GB~16GB
分块处理:例如程序需要28GB,假设MIC卡每次可以使用的内存为7GB,那么需要分4次计算
改变并行层次:
--并行内层循环
外层循环-->内层循环(线程间数据共享)
--任务级并行-->数据级并行
减少申请次数
关键:开辟空间操作放在循环之外
三:数据传输优化:Nocopy(参见上一节《MIC C编程》)
异步传输:MIC在做的时候CPU也可以做
示例:
#pragma offload_transfer target(mic:0) in(in1:length(count) alloc_if(0) free_if(0)) signal(in1)
for(i=0;i<iter;i++)
{
if(i%2==0){
#pragma offload target(mic:0) nocopy(in1) wait(in1) out(out1:length(count) alloc_if(0) free_if(0))
compute(in1,out1);}
else{
#pragma offload_transfer target(mic:0) if(i!=iter-1) in(in1:length(count) alloc_if(0) free_if(0)) signal(in1)
#pragma offload target(mic:0) nocopy(in2) wait(in2) out(out2:length(count) alloc_if(0) free_if(0))
compute(in2,out2);
}
}
异步计算:CPU与MIC异步,计算和I/O异步
int counter;
float *in1;
counter=10000;
_attributes_((target(mic))) mic_compute;
while(counter>0)
{
#pragma offload target(mic:0) signal(in1)
{
mic_compute();
}
cpu_compute(); //此时本函数与上面的MIC函数并行执行
#pragma offload_wait target(mic:0) wait(in)
counter--;
}
SCIF(VS offload)
擅长小数据传输
如果传输的数据是1K、2K、3K,那么SCIF/offload=80%
如果传输的数据>4K,两者效率差不多
如果传输的数据>6K,SCIF大幅度下降
五:存储器访问优化
MIC存储器访问优化策略
--隐藏存储器访问延迟
多线程
预取
--利用Cache优化
时间局部性
空间局部性
--对齐
Cache优化方法:
代码变换
--循环融合
--循环分割
--循环分块
--循环交换
数据交换
--数据放置
--数据重组
循环融合
//original loop;
for(i=0;i<n;i++)
a[i]=b[i]+1;
for(i=0;i<n;i++)
c[i]=a[i]/2;
融合:
//fused loop
for(i=0;i<n;i++)
{
a[i]=b[i]+1;
c[i]=a[i]/2;
}
循环分割:
//original loop
for(i=1;i<n;i++)
{
a[i]=a[i]+b[i-1];
b[i]=c[i-1]*x*y;
c[i]=1/b[i];
d[i]=sqrt(c[i]);
}
分割:
//splitted loop
for(i=1;i<n;i++)
{
b[i]=c[i-1]*x*y;
c[i]=1/b[i];
}
for(i=1;i<n;i++)
a[i]=a[i]+b[i-1];
for(i=1;i<n;i++)
d[i]=sqrt(c[i]);
循环分块:
//original loop
for(i=0;i<n;i++)
for(j=0;j<m;j++)
x[i][j]=y[i]+z[j];
分块:
//tiled loop
for(it=0;it<n;it+=nb)
for(jt=0;jt<m;jt+=mb)
for(i=it;i<min(it+nb,n);i++)
for(j=jt;jt<min(jt+mb,m);j++)
x[i][j]=y[i]+z[j];
循环交换:
//original loop
for(j=0;j<m;j++)
for(i=0;i<n;i++)
c[i][j]=a[i][j]+b[j][i];
一般用于矩阵运算,问题:访问不连续
//interchanged loop
for(i=0;i<n;i++)
for(j=0;j<m;j++)
c[i][j]=a[i][j]+b[i][j];
六:向量化优化(VPU进行批操作)
Intel自动向量化,512/32 处理16个单精度
自动向量化:
什么样的循环可以自动向量化?
1.编译器认为循环内的每条语句之间都没有依赖关系且没有循环依赖
2.最内层循环
3.数据类型尽量一致
一般不会自动向量化
1.for(int i=0;i<N;i++)
a[i]=a[i-1]+b[i];
2.for(int i=0;i<N;i++)
a[c[i]]=b[d[i]];
3.for(int i=0;i<N;i++)
a[i]=foo(b[i]);
4.迭代次数不确定
查看是否真的向量化了:
-qopt -report=[=n]
-qopt-report[n] |
含义 |
n=0 |
不显示诊断信息 |
n=1 |
只显示已向量化的循环(默认值) |
n=2 |
显示已向量化和未向量化的循环 |
n=3 |
显示已向量化和未向量化的循环以及数据依赖信息 |
n=4 |
只显示为向量化的循环 |
n=5 |
显示未向量化的循环以及数据依赖信息 |
引导向量化策略
插入引语自动向量化:不改变原程序结构,只需要插入预编译指令(引语)即可自动向量化。
调整程序循环结构并插入引语自动向量化:对源程序做一些结构调整,如嵌套循环交换次序等,然后插入引语可以自动向量化。
编写SIMD指令:SIMD指令可以比自动向量化获得更好地性能,但针对不同的硬件平台编写的SIMD指令也不同,并且SIMD指令易读性较差,所以SIMD指令可以选择性使用。
自动向量化:优势
提高性能:向量化处理,实现了单指令周期同时处理多批数据
编写单一版本的代码,减少使用汇编使编码工作简化:较少的汇编意味着会大大减少为特定的系统编程的工作,程序将很容易升级并使用于最新的主流系统而不必重新编写那些汇编代码。
#pragma ivdep //建议向量化
#pragma simd //强制向量化
C99 加-restrict
#pragma vector always //如果引语不成功
指定循环向量化的方式,避免一些没有内存对其的操作没有被向量化
七:负载均衡优化
用于多节点多卡
集群多节点间并行框架
主函数:
#define N(10000)
_global_void kernel();
void main()
{
int calc_num,calc_len,rank,size;
MPI_Init(argc,argv);
MPI_Comm_size(MPI_COMM_WORLD,&size);//总节点数
MPI_Comm_rank(MPI_COMM_WORLD,&rank);//节点号
calc_num=......;calc_len=......;//计算运算的元素数
int *a=(int *)malloc(calc_len*sizeof(int));
if(rank==0) MPI_Send();//主节点分发数据
else MPI_Recv();//子节点接收数据
main_calc();
MPI::Finalize(); free(a);
return 0;
}
单节点多卡的编程框架
外围框架伪代码
int device_num=M+1;//M为单节点上GPU(MIC)卡的数目
omp_set_nested(true);//允许OpenMP嵌套
#pragma omp parallel for private(...),num_threads(device_num) //卡与CPU同时计算
{for(i=0;i<device_num;i++)
{
if(i==0)
{
//CPU端计算
int j;
#pragma omp parallel for private(...)
for(int j=0;j<device_num;j++)
a[k]=k+6;
}
else{
MIC_kernel();
}
}
}
设备内负载均衡
通过调度算法(static、dynamic、guided等,已经学过,略过)
其余方法略。