MIC C编程(offload模式)
编程特点
简单---隐藏大量细节,语法与OpenMPI类似(不需要开辟空间)
灵活---OpenMP MPI(但是用的不多)pThread等多种方式
传统---与CPU编程一脉相承
MIC C扩展语言结构
编译指导方式(#pragma)
offload
--表示之后的代码段将使用offload模式运行
运行在其他设备上(MIC)
--标识内存传递参数,传递过程对用户透明
不需要手动写代码控制何时传入、何时传出
不需要手动申请卡上内存空间
不需要讲卡上内存与主机端内存空间手动对应
简单示例
#pragma offload target(mic)
for(i=0;i<LEN;i++)
{
printf(“Index:%d\n”,i);
}
循环和printf语句在MIC上执行,没有数据传输
此时循环是串行的,因为没有并行引语
offload语法
#pragma offload specifier[,specifier...]
specifier |
示例 |
含义 |
target |
target(mic:0) |
使用什么设备运行 |
if |
if(N>1000) |
是否使用该设备 |
in |
in(p:length(LEN) alloc_if(1)) |
数据传入device |
out |
out(p:length(LEN)) |
数据从device传出 |
inout |
inout(p:length(LEN) align(8)) |
数据既传入又传出 |
nocopy |
nocopy(p) |
只开辟空间不传递 |
signal |
signal(tag) |
发送信号 |
wait |
wait(tag1,tag2) |
等待信号 |
mandatory |
mandatory |
必须用MIC运行 |
其中in/out/inout/nocopy可用的属性有
属性 |
示例 |
含义 |
length |
length(LEN) |
元素个数 |
alloc_if |
alloc_if(1) |
是否开辟内存 |
free_if |
free_if(N>0) |
offload后是否释放 |
align |
align(8) |
对齐 |
alloc |
alloc(p[10:100]) |
开辟数组一部分 |
into |
into(p[10:100]) |
传递数组一部分 |
语法详解target
#pragma offload target(mic) mic这里目前只支持MIC卡
--这是必须使用的属性
--表示下面的代码段使用MIC运行
--现阶段,target的参数只支持“mic”
--使用哪块卡,由驱动决定,如果系统没有MIC卡,则使用CPU运行
target(mic:num)
--num>=-1;当为-1时,系统自动选择一块mic卡,如果1号卡不存在或者有故障,则程序报错退出
--设备号=num%mic数量(也就是说num可以大于mic卡数,循环的)
语法详解in
#pragma offload target(mic) in(array) --传入CPU中的array,不负责传出
--可选属性
--可附加length、alloc_if、free_if、align、alloc、into属性
--表示进入offload区域时,将数组array从CPU内存中传递到MIC内存中,只传入不传出
--只需要传递数组,标量会自动以inout方式传递
语法详解out/inout/nocopy
可选属性
与in基本相似
out表示离开offload区域时,将数组array从MIC内存传递到CPU内存,只传出不传入
inout表示进入offload区域时,将数组从CPU内存传输到MIC内存,离开时将数组传回
nocopy表示仅开辟或保留空间,不传输数据
注意事项:
array是静态声明时,不用加长度参数
array是动态开辟时(堆),需要加长度参数length
array长度相同,或静态声明时,可以在一个传输区域内传输多个。例:in(a,b)
默认进入offload时开辟内存,离开时释放
in/out/inout/nocopy在一句offload中可以出现多次,但每个数组名只能出现一次
传输属性详解length
传输数组的元素个数,非数组长度!
只能使用在动态开辟的数组中
数组长度相同时可以共用
示例:
float *pArray,*pArray1;
pArray=(float*)malloc(LEN*sizeof(float));
pArray1=(float*)malloc(LEN*sizeof(float));
#pragma offload target \
in(pArray,pArray1:length(LEN)) 共用啦
{……}
传输属性详解alloc_if,free_if
控制是否在传输前开辟卡上内存空间和传完是否释放
常结合nocopy属性应用
例:
#pragma offload target \
in(pArray:length(LEN) free_if(0)) 里面是boolean型,“0”表示不释放空间
//进入下一个offload时,pArray的数据仍然存在(计算完后pArray存在于MIC中,不需要再从CPU端传输多MIC端了)
#pragma offload target \
nocopy(pArray:length(LEN) alloc_if(0)) 不用再释放空间了,也不用in再次传输了
//此时不需要开辟空间,否则会覆盖原有数据
(这里优化点:1:传输 2:开辟 3:释放)
数据传输优化Nocopy
p_c=...//p_c在每次迭代中值不变
for(i=0;i<steps;i++) //迭代次数
{
p_in=...;//每次迭代计算时,p_in的值变化
#pragma offload target(mic) \
in(p_in:length(...)) in(p_c:length(...)) out(p_out:lenth(...))
{
kernel(p_in,p_c,p_out);
}
}
...=p_out;//CPU端在所有迭代完成之后才用到p_out的值
这里的冗余是p_c冗余传入p_out冗余输出
优化方案:
p_c=...;//p_c在每次迭代中值不改变
#pragma offload target(mic) \
in(p_c:length(...) alloc_if(1) free_if(0)) \ //开辟不释放
nocopy(p_in:length(...) alloc_if(1) free_if(0)) \
nocopy(p_out:length(...) alloc_if(1) free_if(0))
{
}//申请空间,并且不释放,传递p_c的值
for(i=0;i<steps;i++){//迭代多次
p_in=...;
#pragma offload target(mic) \
in(p_in:length(...) alloc_if(0) free_if(0)) \ //p_in省去了steps次的开辟释放空间
nocopy(p_c) nocopy(p_out) //这里p_c省去了输入,p_out省去了输出
{
kernel(p_in,p_c,p_out);
}}
//以下是把最后的p_out输出来
#pragma offload target(mic) \
nocopy(p_c:length(...) alloc_if(0) free_if(1)) \
nocopy(p_in:length(...) alloc_if(0) free_if(1)) \
out(p_out:length(...) alloc_if(0) free_if(1))
{...}
...=p_out
传输属性详解align
控制卡上内存开辟的对齐大小
单位为字节
内存不对齐时会影响性能
一般用于传输不规则的结构体(定义顺序)
传输属性详解alloc/into
alloc开辟卡上内存,大小为数组的一部分
into传输数据,大小为数组的一部分
不能与inout/nocopy同用
传输属性特殊用法
#pragma offload target \
in(p[10:100]:alloc(p[5:1000]))
在设备开辟了1000个元素的数组p,数组下表的可用范围是从5开始,即5-10004.然后将主机端从p[10]开始的100个元素传到设备端的p[10]-p[109]的位置(mic卡上)
需要程序员保证不会越界
#pragma offload ... \
in(p[0:500]:into(p1[500:500]))
将主机端p[0]开始的500个元素的值,复制到设备端p1[500]-p1[999]的响应位置
如果同一offload语句有多个into,执行顺序未知,因此需要注意目的数组范围不能重叠
into不是简单拷贝,维度不同不能传输
语法详解signal/wait
#pragma offload target(mic) signal(tag)
可选属性,异步传输
等同于offload_transfer/offload_wait,但后者为单一语句,后面不跟代码段
在传输语句时加signal,CPU不等待MIC运行结束即继续运行。知道遇到wait语句
wait语句等待signal语句传输完成再继续运行
参数为一个变量或者数组名(即使同事传递多个)
示例:
float *in1;
#pragma offload target(mic:0) signal(in1)
{
mic_compute();
}
cpu_compute(); //本函数与上面的MIC函数并行执行
#pragma offload_wait target(mic:0) wait(in1) //这里与MIC合流
offload+OpenMP
在offload语句作用范围内使用OpenMp,即可在MIC上并行计算
例:
#pragma offload target(mic) out(arr:length(LEN))
#pragma omp parallel for
for(i=0;i<LEN;++i)
{
arr[i]=i*3.0/x;
}
变量和函数声明
_declspec(target(mic))
_attribute_((target(mic)))
修饰后,变量和函数可同时用于CPU和MIC,二者等价,attribute时注意有两层括号
例:
_attribute_((target(mic))) int a;
-attribute_((target(mic))) void fun();
变量和函数声明(批量)
同时声明多个变量和函数
两种形式
#pragma offload_attribute(push,target(mic))
//函数或变量声明
#pragma offload_attribute(pop)
或者:
#pragma offload_attribute(target(mic))
//函数或变量声明
#pragma offload_attribute(target(none))
判断代码段是否运行在MIC上
检查是否定义了宏_MIC_
#ifdef_MIC_
...
#else
...
#endif
需要封装在函数中,而不能直接用于offload代码段
_attribute_((target(mic))) void offload_check(void)
{
#ifdef _MIC_
printf(“check func:Run on the mic!\n”);
#else
printf(“check func:Run on the cpu!\n”);
#endif
}
语法详解if
#pragma offload target(mic) if(flag) //flag 是boolean型的
可选属性
如果flag为真,则使用MIC执行代码段,否则使用CPU执行
例:#pragma offload target(mic) if(N>100)
表示如果N大于100就在MIC卡上执行
可用于不同规模数据,或者CPU、MIC协同计算的情况
语法详解mandatory
可选属性
没有参数
强制下面的代码段在MIC上运行,如果MIC设备不可用,则报错退出--不去CPU上跑
多用于必须在MIC上运行的代码,例如使用SIMD写的代码段
程序编译
使用Intel编译器
编译选项与原CPU程序完全一样
如果只想使用CPU,添加选项”-no-offload”
如果使用了OpenMP,添加选项”-openmp” (如果不加此选项,就在mic的单核上运行)