HPC Game 0th. 作为HPC的小练习来完成,因为0th是一年前了,所以记录自己的解题应该没有问题。因为提供的超算平台现在不可以用了,相关文档现在也无法访问了,所以是直接做的题目。注册还可以看到题目。
主页:https://hpcgame.pku.edu.cn/
2024.1.16更新:
才只做了一点,现在主页进去已经是HPC Game 1th了,开始时间是下周,这次可以跟着参加尝试一下了,本篇也不用更新了。
一.高性能计算简介 1.实验室的新机器 本题的要求是编写一个脚本在集群上运行程序。
Slurm是一个开源的集群资源管理和作业调度系统,可以在Linux集群上运行各种并行程序,如MPI等。一些相关的信息可查看上交超算平台手册-Slurm作业调度系统 或快速入门 。
根据要求编写一个脚本启动程序,因为题目中说明不会使用sbatch,所以使用srun运行。
1 2 3 #!/bin/bash srun -p compute -N1 -n1 -c1 ./program $1 > output.dat seff $(cat job_id.dat) > seff.dat
如果用sbatch启动:1 2 3 4 5 6 7 8 9 10 #!/bin/bash ./program $1 seff $(cat job_id.dat) > seff.dat
2.基础知识问答
TOP500中能效最高的超算:能效最高的应该是Henri,性能最高的是Frontier。
戈登贝尔奖是并行计算领域的最高奖项,设立于1987年。中国人第一次获得戈登贝尔奖是2016年,第一作者是杨超,现工作于计算所。
天河二号采用的是Xeon E5和Xeon Phi处理器,太湖之光采用申威众核处理器。TOP500曾排名最高的是太湖之光。
一般来说,服务器使用的 CPU 相比于同代同等级的游戏 CPU,核心更多,多核性能更强。单核频率更低,单核性能更弱。
5-7题:
假设单位内存是1,内存写为直写,全相联,块大小为1,替换策略为LRU,对于如下的矩阵乘法:
1 2 3 4 for m in [1 , N]: for n in [1 ,N]: for k in [1 , N]: C[m,n] = C[m,n] + A[m, k] * B[k, n]
5.对ABC均进行了N^3次读,对C进行了N^3次写。
6.如果有一个一级cache处理器,cache大小为3*N^2,运算开始时ABC在内存,结束后强制将cache写回内存(直写)。则会对内存进行3N^2次读运算,N^3+3N^2次写运算。
7.如果6中的cache大小为2N+1,则会对内存进行3N^3次读,N^3+2N+1次写。
3.简单题 本题的要求是使用P个核心,把N个 32 位有符号整数数组中所有元素 + 1,并且求原来(未加一)的数字之和(MOD 100001651)并输出。
这里复习一下mmap和文件操作,fstream的文件如果要mmap需要int fd = fs.rdbuf()->fd()转为整型,转完就可以把文件流关掉了,然后就可以mmap了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 #include <iostream> #include <sys/mman.h> #include <sys/stat.h> #include <fcntl.h> #include <unistd.h> #include <omp.h> using namespace std;int main () { int fd1 = open ("input1.bin" , O_RDONLY); int fd2 = open ("output.bin" , O_RDWR | O_CREAT, 0666 ); if (fd1 == -1 || fd2 == -1 ) { cout << "Open file error." << endl; return 0 ; } struct stat st; int r = fstat (fd1, &st); if (r == -1 ) { cout << "Get file size error." << endl; close (fd1); close (fd2); return 0 ; } int size = st.st_size; ftruncate (fd2, size-4 ); void *p1 = mmap (NULL , size, PROT_READ, MAP_PRIVATE, fd1, 0 ); if (p1 == MAP_FAILED ) { cout << "Map file error." << endl; close (fd1); close (fd2); return 0 ; } void *p2 = mmap (NULL , size-4 , PROT_READ | PROT_WRITE, MAP_SHARED, fd2, 0 ); if (p2 == MAP_FAILED ) { cout << "Map file error." << endl; close (fd1); close (fd2); return 0 ; } int *input = (int *)p1; int *output = (int *)p2; int p = *input; int n = *(input + 1 ); cout << "p = " << p << ", n = " << n << endl; int sum = 0 ; #pragma omp prallel for reduction(+: sum) num_threads(p) for (int i=0 ;i<n;i++){ int num = *(input+2 +i); sum = (sum+num)%100001651 ; *(output+1 +i) = num; } *output = sum%100001651 ; cout<<"sum = " <<sum<<endl; msync (p2, size-4 , MS_SYNC); munmap (p1, size); munmap (p2, size-4 ); close (fd1); close (fd2); return 0 ; }
二.并行与大规模 1.求积分 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 #include "rhs.h" #include <omp.h> #include <fstream> #include <stdlib.h> #include <iostream> #include <iomanip> using namespace std;const int g_ncore = omp_get_num_procs (); int dtn (int n, int min_iteration) { int max_threads = n/min_iteration; return max_threads>g_ncore?g_ncore:max (max_threads,1 ); } int main (int argc, char *argv[]) { if (argc<2 ){ cout << "usage: main <N>" <<endl; return 1 ; } int N = atoi (argv[1 ]); ofstream fs ("output.dat" ) ; double sum = 0 ; #pragma omp parallel for reduction(+:sum) num_threads(dtn(N, 1e3)) for (int i=1 ;i<=N;i++){ double x = (i-0.5 )/N; double res; rhs (x,res); sum += res; } fs << fixed << setprecision (12 ) << sum << "\n" ; fs.close (); return 0 ; }
2.乘一乘 写的时候遇到C++小问题,rhs提供的函数参数是unsigned int的引用,如果传int进去,会被转换为unsigned,变成临时值,会变成右值。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 typedef unsigned int uint;const int MAXN = 1e5 +5 ;const int g_ncore = omp_get_num_procs ();int dtn (int n, int min_iteration) { int max_threads = n/min_iteration; return max_threads>g_ncore?g_ncore:max (max_threads,1 ); } void matrix_mul_parallel (double **result, int N1, int N2, int N3) { clock_t start, end; start = clock (); #pragma omp parallel for num_threads(dtn(N1,10)) for (uint i=0 ;i<(uint)N1;i++){ for (uint k=0 ;k<(uint)N2;k++){ for (uint j=0 ;j<(uint)N3;j++){ double va, vb; matA (i,k,va); matB (k,j,vb); result[i][j] += va*vb; } } } end = clock (); cout << "matrix_mul_parallel time = " << double (end - start) / CLOCKS_PER_SEC << "s" << endl; }
这次还尝试了一下collapse和只对第一层循环并行的比较,时间结果如下:1 2 3 4 5 6 7 8 ./omp_cmul 100 1000 1000 matrix_mul time = 7.333s matrix_mul_parallel time = 0.91s matrix_mul_collapse time = 7.479s PS C:\Users\Desktop\tmp\hpcgame\q2b> ./omp_cmul 1000 1000 1000 matrix_mul time = 73.78s matrix_mul_parallel time = 8.02s matrix_mul_collapse time = 9.282s
矩阵乘是有访问的空间局部性的,并且每一层循环内的负载是均衡的,因此将循环第一层并行化更快,collapse的效果和矩阵的形状有很大关系,但尝试的一些形状都比将第一层循环并行化更慢。
3.解方程 其实f()的值在每轮迭代都是一样的,可以先处理出来,不用每次算,但是反正也没办法交到平台测性能,这里就不优化了。遇到的一个小tip是reduction操作会算上原来的值,因此要注意进入并行循环之前的值 。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 void possion (double mat[MAXN][MAXN], int N) { for (int i=0 ;i<=N;i++){ for (int j=0 ;j<=N;j++) mat[i][j] = 0.0 ; } double eps; do { eps = 1e-16 ; #pragma omp parallel for reduction(max:eps) num_threads(dtn(N*N, 20)) for (int i=1 ;i<N;i++){ for (int j=1 ;j<N;j++){ double xi, yi, va, tmp; xi = i/(double )N; yi = j/(double )N; rhs (xi, yi, va); tmp = mat[i][j]; mat[i][j] = (mat[i-1 ][j]+mat[i+1 ][j]+mat[i][j-1 ]+mat[i][j+1 ]+va/(double )N/(double )N)/4.0 ; eps = (eps<1e-15 )?abs (mat[i][j]-tmp):max (abs (mat[i][j]-tmp),eps); } } }while (eps>1e-15 ); }