MIC并没有单独的编程语言,MIC编程是对C/C++/Fortran语言的扩展,其使用了编译制导语句。它十分类似于OpenMP,不过MPSS(Intel MIC Platform Software Stack,英特尔MIC平台软件栈)也提供了一些高级API函数接口以便满足不同需求。这篇文章主要对offload模式的MIC编程语法进行一个简单学习。
offload模式
关键词offload的作用:在offload作用范围内的程序代码是要在MIC卡上运行的。offload语句用于CPU与MIC的主从模式。
offload基本语句
|
|
SIMD模型:如果一个程序的主要代码集中在for循环中,那么他就是典型的SIMD,即每次循环迭代,都拥有相同的指令,只是数据各不相同。
Tips:
- 移植到MIC上,只需加上上面的编译制导语句
- 编译时不需要任何特殊的编译选项,默认为MIC程序,若要编译成CPU程序,则需要加入
-no-offload
。 - 普通的循环程序代码加上
offload
语句后,其在MIC上仍然是串行执行的(只用到了MIC一个核的一个硬件线程)。offload语句本身只是起到了指示编译器将代码放入设备端运行的作用,并不指示代码并行执行。若想实现在MIC上的并行,则需要在设备端使用OpenMP。
程序移植所需工作
- 移植到MIC上,只需加上上面的编译制导语句
- 编译时不需要任何特殊的编译选项,默认为MIC程序,若要编译成CPU程序,则需要加入
-no-offload
。 - 普通的循环程序代码加上
offload
语句后,其在MIC上仍然是串行执行的(只用到了MIC一个核的一个硬件线程)。offload语句本身只是起到了指示编译器将代码放入设备端运行的作用,并不指示代码并行执行。若想实现在MIC上的并行,则需要在设备端使用OpenMP。
Tips:判断程序是否在MIC执行:MIC提供了一个检查宏_MIC_(相关语法在后面涉及)
|
|
MIC数据传输
Tips:
- 不建议在设备端使用打印或是输出语句
- 使用引语编程的一个好处便是使用一套代码就可以满足不同模型,只要不加上相应的编译选项,就不会使用该特性
关键字
out:通知编译器,括号内的变量/数组是需要输出的,这样驱动就会在代码离开MIC卡时,将变量拷贝到内存中相应的位置中。
注意:
- 若是在栈上声明的数组,其长度在编译时就已经确定,所以不需要在传输时标注长度,但是若是在堆上声明的数组,由于编译时大小不能确定,因此必须在传输时标注数组大小。关于堆栈的内容可以参考我的另外一篇博文:C/C++中堆与栈简析
- 非数组的变量若不显式传值,一旦MIC用到,就会自动以inout方式传递。
in:输入,在设备端开辟空间并将主机端数据复制到设备端
inout:输入并输出,在设备端开辟空间,在进入设备端时将数据复制到设备端,在从设备端离开时,将数据从设备端复制到主机端。
nocopy:不拷贝。仅在设备端建立空间,不复制数据。但是需要在CPU端声明
12_attribute_((target(mic)))float* a; //a是中间变量,因此不需要再CPU端申请空间,但是必须声明为全局变量,且加上attribute前缀。Tips:nocopy使用场合
- 在不同的offload区域(offload两次),如果后一次需要用到的前一次计算后的某些变量和数据,则可以使用nocopy避免数据内存中转,造成浪费(
#pragma offload nocopy(a)
)
- 在不同的offload区域(offload两次),如果后一次需要用到的前一次计算后的某些变量和数据,则可以使用nocopy避免数据内存中转,造成浪费(
- 变量作为设备代码段中的临时变量(不需从主机赋初值,也不需传回主机)
Rule of thumbs
一般规则
- 传输关键字可以有多个或零个,当多个时:可以连续书写,也可以用逗号或空格隔开。相同的关键字可以在一个offload语句中出现多次,但相同的变量名不可以在一个offload中出现多次(即使是在不同的关键字中);
- 传输关键字后跟括号,括号内为变量名;
- 变量应为数组名或是指针(特指动态数组指针)或普通标量,多个变量之间用逗号隔开;
- 变量为指针时,指针只能指向非指针变量,即不支持二维指针。
- 变量为数组或是指向数组的指针时,可以指定数组起始与长度;
- 变量为指针时,需要在变量名后面加上”:length(len)”,其中len为动态数组的元素个数,若多个动态数组长度相同,则可以写在一起,如:in(a,b,c:length(20))。元素个数可以是变量
- 除了length以外,还有alloc_if、free_if、align、alloc、into等五个关键字,一个传输关键字用一个冒号即可
- alloc_if和free_if的参数是判断表达式,其计算结果应该是布尔型。若alloc_if的参数结果为真则在进入设备端是为前述变量开辟空间,若free_if的参数结果为真则在离开设备端时为前述变量释放空间。
- align的参数时一个正整数,且必须是2的正整数次幂,其含义为在设备端开辟的前述变量,以align参数的长度对齐
- alloc的参数是数组名,含义是创建指定的部分内存空间
- into的参数是变量或是数组名,但只能一对一传递,含义为将数组中主机端拷贝到设备端的另一个数组,或相反。into可以和alloc,alloc_if,free_if结合使用,但不能与inout,nocopy同时使用。
示例讲解:P92
in/out/inout的实用语法
|
|
8:最常用的数组引用方法,传输全部数据
9:传输数组a的一部分,其中
[i:j]
规范第一维,i
表示起始位置,j
表示个数,第二维中只有冒号,省略了前后,表示第二维是完整的。长度参数可以是变量。10:即使传输的是指向动态数组的指针,也可以用数组的“[ ]”表示。
11:第一维只有一个参数5,意味第一维只有一个元素
12:表示可以传输结构体的一部分
注意:需要注意的是,虽然传输的是数组的一部分,但在MIC卡端开辟内存空间时,仍然开辟了从第一个元素开始的全部空间,所以一方面这种写法并没有减少内存使用,另一方面在使用时仍然要将数组视为整体使用。
针对此种写法的关键字:alloc和into
由于传输部分数组的语法会开辟全部的内存空间,因此可以使用alloc语法限定开辟的空间范围
1234/* 下述语句:* ①在设备端开辟1000个元素的数组p,数组下标从5开始,到1004;* ②将主机端的p[10]-p[109]传到设备端的p[10]-p[109],检查数据越界的责任仍然是程序员。 */pragma offload ... in (p[10:100]:alloc(p[5:1000])into语句可以将主机端的数据数组一部分传递给另一个设备数组
12/* 使用这种方式需要注意数组覆盖的情况。另外还要注意的就是into不能实现不同维度数组间的数据传递。*/
其他一些关键字
target
target(mic):mic
是目前唯一合法的取值。在运行时可以指定使用第几块mic卡,用法是在mic后加冒号并附加序号,如target(mic:1),序号应注意:- 当序号大于等于0时,程序将offload到相应设备设备上,设备号计算方法:设备号=序号mod总设备数(取余)
- 当序号等于-1时,系统自动选择计算设备
- 序号不可小于-1
使用多块MIC卡协同计算的OpenMP循环代码
1234567omp_set_nested(1); //嵌套OpenMP代码for(i=0;i<3;i++){...}以上代码将第一份任务指定给了CPU,第2,3份指定给了两块不同的MIC卡。由于使用了嵌套OpenMP代码,因此需要调用第一句。
在offload之前,可以使用API函数
int\_Offload\_number\_of\_devices(void)
确定系统拥有的MIC设备数。在offload的代码段里,可以使用API函数int\_Offload\_get\_device\_number(void)
获取该代码段所在的设备编号。
if
根据条件判断是否要将代码段放到设备上运行,若表达式为真,则在MIC端运行,否则在CPU端运行(如上代码)
mandatory
- 表示该代码必须在MIC上运行,若设备不可用,直接报错。
- 不可与if同时使用。
异步传输
- signal和wait:即CPU端无需等待offload语句返回,即可异步运行下面的代码。一般用于启动MIC代码段后,并发执行CPU代码,达到同步执行的目的
- offload_transfer和offload_wait:与offload类似,只负责数据传输,后面不加入计算代码。其中offload_transfer支持的参数与offload语句相同,但offload只支持target、if、wait三个参数
- 用法:
- signal语句在offload语句代码段结束后发送一个信号,wait语句负责接收,所以二者一定是成对使用
- wait语句可以一次等待多个信号,所以二者语句数量未必相等
- signal 和 wait的参数是tag,在C中,是数组指针,同时传输多个数组指针时,能且只能signal/wait一个数组名。在Fortran中,该变量是一个整型变量。
offload语法总结
|
|
其中,specifier可填入:target,if,in,out,inout,nocopy,signal,wait,mandatory
,而在in/out/inout/nocopy
可用属性有:length,alloc_if,free_if,align,alloc,into
。
变量与函数声明
单个声明方式
|
|
批量声明方式
|
|
注意:
- 在C/C++中,使用attribute的话,需要注意target外围是两层括号,只写一层是错的
- 上述声明方式适用于函数与变量,变量是全局变量
- 函数和变量可以同时用于CPU与MIC
MIC程序编译运行注意事项
头文件
- 在C/C++中,在MIC中若要用到API,需包含offload.h
- 在Fortran中,需要include mic_lib.f90或USE mic_lib
环境变量
MIC_STACKSIZE:规定MIC上每个线程所占的栈的大小,默认2MB,可以修改:
1export MIC_STACKSIZE=5MMIC_ENV_PREFIX:可以设置MIC专属环境变量的前缀,以区分MIC端和CPU端的环境变量,若不加以却别,默认情况下,CPU端的环境变量会作用于MIC端,示例:
123export OMP_NUM_THREADS=8export MIC_OMP_NUM_THREADS=124export MIC_ENV_PREFIX=MIC_使用方法如下:定义一个前缀“MIC_”,若MIC上有环境变量符合前缀,则去掉前缀,并将其作用于MIC上。
MIC_LD_LIBRARY_PATH:定义了程序在MIC上运行时所需要用到的共享库路径,此处的共享库路径指MIC卡上的地址,即需要手动将共享库传输到MIC端,路径一般指向用户自己编写的共享库。由于主机端的LD_LIBRARY_PATH不会自动扩展到MIC上,因此不需要结合上面的环境变量
编译选项
-mmic
:表示编译出的程序只能在MIC上运行。默认不开启,不开启时编译产生的程序是异构程序。-no-offload
:表示编译出的程序只能在CPU上运行,使用这个选项时,将忽略程序中所有与MIC相关的语句。默认不开启,开启时编译产生的程序是纯CPU程序。-offload-attribute-target=mic
:表示所有未声明为mic可用的函数和全局变量都可以在MIC中使用,与在代码中使用attribute属性声明函数或变量的效果相同。默认不开启。-offload-option,target,tool,"option-list"
:作用为针对被offload的对象,即在MIC卡上运行的代码段,使用特殊的编译选项。其中target只能选mic,tool只能取ld(链接库时用到的程序),as(汇编器)或compiler(编译器)之一,option-list是具体的选项,必须用引号括起来,选项之间用空格分开。
注意:
- MIC程序的编译选项与CPU程序无多大差别,绝大多数的CPU程序编译选项也能在MIC程序中使用
- 异构程序编译时,使用的编译选项会全部传递给主机端编译程序。有一部分(MIC支持的部分)会被传递给MIC端编译程序,可以通过
"-watch=mic-cmd"
查看 - 使用offload-option(上述第4点)增加的编译选项,只能在MIC端使用,并且会覆盖或附加在CPU端过去的编译选项之后。若想覆盖,则需要将-offload-load写在自动传输的参数前面,若-offload-option选项被写在后面,则会附加到自动传输的参数后面。
其他问题
- 在offload区域只能正常退出,使用exit()会报错
- 在Fortran中在外部函数/subroutine中定义的变量不能通过offload语句传递给内部/subroutine,但是SAVE变量不受约束。
参考资料:
- MIC高性能计算编程指南,王恩东,中国水利水电出版社,2012