用法介绍
1. “hbm” 和 “nohbm” 关键字属性
全局变量
1 2 | int __attribute__((hbm)) GlobalHBMArr[100]; int __attribute__((nohbm))GlobalNoHBMArr[100]; |
局部变量
1 2 | int *__attribute__((nohbm)) Ptr1 = (int *)malloc(100*sizeof(int)); int * __attribute__((hbm)) Ptr2 = (int *)malloc(100*sizeof(int)); |
函数参数
1 2 3 4 5 | void foo(int * __attribute__((nohbm))Output, int * __attribute__((hbm)) Input) { for (int i = 0; i < 100; i++) { Output[i] = i * (i + 1) + Input[i]; } } |
5.0.0版本起新增支持flat模式下通过导语将全局数组和栈数组内存申请至HBM功能,编译器默认使能,用户可以通过添加”-mllvm -enable-gv-hbm-attr=false”和”-mllvm -enable-lsv-hbm-attr=false”手动关闭。
2. __builtin_hbm_prefetch(address, distance)
address: 需要预取的数据,指针或数组, 必选参数
distance:预取提前量,指的是提前的cycle数, 必选参数
1 2 3 4 5 | // 预取数据ArrB,提前量为100 for(int i = 0; i < 1000; i++) { __builtin_hbm_prefetch(ArrB, 100); ArrA[i] += ArrB[i] + ArrC[i]; } |
3. pragma clang loop prefetch(variable, locality, distance)
variable: 必选参数。要预取的数据,必须是一个已经声明的指针或数组变量,支持load/store访存和intrinsic类型访存。
locality: 可选参数。指定cache级别,如果不指定,则编译器自动判断。‘0’: no reuse , ‘1’ : L1 cache , ‘2’: L2 cache , ‘3’: L3 cache, ‘4’: HBM cache
distance: 可选参数。指定循环迭代提前量。使用该参数,必须指定locality。参数必须为大于0的整数。
pragma clang loop noprefetch(variable)
variable: 必选参数。不做预取优化的数据,必须是一个已经声明的指针或数组变量。
1 2 3 4 5 6 7 8 9 10 11 12 | // 预取数据"a"、"b"到HBM缓存, 预取提前量为默认值 #pragma clang loop prefetch(a, 4) #pragma clang loop prefetch(b, 4) for (int i = 0; i < n; i++) { sum += a[i] + b[i]; } // 数据"a"不做软件预取优化 #pragma clang loop noprefetch(a) for (int i = 0; i < n; i++) { sum += a[i] + b[i]; } |
Fortran当前也支持预取导语,对应的格式是:
1 2 | !$mem prefetch(variable, locality, distance) !$mem noprefetch(variable) |
4.编译器自动预取选项“-fhbm-mode=cache”
添加该选项编译器会分析函数中循环的访存特征,并根据代价模型对数据进行优先级排序,并为优先级高的数据插入预取指令。编译器代价模型中包含以下因素:
- 循环在并行域中,比如OpenMP导语指定的并行区域
- 访存特征:直接访存、间接访存,间接访存计算权重更大
- 访存stride越大计算权重越大
- 写内存比读内存有更高的权重
- 循环深度:内层循环计算权重更大
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | // case1中,ApsiPtr,psiPtr是间接访存,具有较高优先级,而ApsiPtr是写内存,所以优先级最高。 // 优先级顺序为ApsiPtr > psiPtr > 其他 void case1(double *diagPtr, double *psiPtr, double *ApsiPtr, int *lPtr, int *uPtr, double *lowerPtr, double *upperPtr, int nFaces) { for (int face=0; face<nFaces; face++) { ApsiPtr[uPtr[face]] += lowerPtr[face]*psiPtr[lPtr[face]]; ApsiPtr[lPtr[face]] += upperPtr[face]*psiPtr[uPtr[face]]; } } // case2中,ApsiPtr两处访存其中一处为写内存,优先级最高。psiPtr两处访存优先级次之 // 优先级顺序为 ApsiPtr > psiPtr > lPtr > diagPtr void case2(double *diagPtr, double *psiPtr, double *ApsiPtr, int *lPtr, int nCells) { for (int cell=0; cell<nCells; cell++) { ApsiPtr[cell] = diagPtr[cell]*psiPtr[cell]; } for (int cell=0; cell<nCells; cell++) { lPtr[cell] = ApsiPtr[cell] * psiPtr[cell]; } } |
用户可以通过一些选项调整编译器cost model:
-mllvm -hbm-prefetch-percentage=$value:指定插入预取指令的比例,数值越大插入的预取指令越多,默认值0.25
-mllvm -hbm-prefetch-max-num=$value: 指定预取的最大数量,默认值10
-mllvm -filter-hbm-functions=$"func1, func2":指定过滤掉的函数名称,对于过滤掉的函数不做自动预取。
-mllvm -loop-prefetch-writes: 指定为写内存操作插入预取,默认值不插入。
-mllvm -indirect-load-prefetch: 指定为间接访存操作插入预取,默认不插入。
5. malloc自动替换选项“-fhbm-mode=flat”
编译器根据代价模型识别需要替换的malloc调用,将其替换为hbw_malloc,用于分配HBM内存。当前只有在OpenMP导语指定的并行区域中存在use的数据会做替换。编译时需要加"-lmemkind"链接选项,并保证环境中包含对应的库。