Apple Silicon M1 Max上多线程Jacobi程序触发Bus Error问题求助
Jacobi算法OpenMP多线程Bus Error排查与解决
问题描述
使用Jacobi算法求解线性方程组,通过OpenMP实现多线程加速时,程序运行出现Bus Error。环境为配备64GB内存的Mac Studio(Apple Silicon M1 Max)。
代码实现
//////////////////////////////////////////////////////////////////////////////// // jacobi.c --- TP2 : resolution d'un systeme lineaire par la methode de jacobi // // Auteur : Jeremie Gaidamour (CNRS/IDRIS) <gaidamou@idris.fr> // Cr�� le : Tue Jul 09 17:33:23 2013 // Dern. mod. par : Jeremie Gaidamour (CNRS/IDRIS) <gaidamou@idris.fr> // Dern. mod. le : Mon Aug 26 14:57:55 2013 //////////////////////////////////////////////////////////////////////////////// #include <stdio.h> #include <stdlib.h> #include <string.h> #include <float.h> #include <math.h> #include <time.h> #include <sys/time.h> #ifdef _OPENMP #include <omp.h> #endif // _OPENMP // Dimension par defaut de la taille des matrices #ifndef VAL_N #define VAL_N 1201 #endif #ifndef VAL_D #define VAL_D 800 #endif // Initialisation aleatoire d'un tableau void random_number(double* array, int size) { for(int i=0; i<size; i++) { // Generation d'un nombre dans l'intervalle [0, 1[ double r = (double)rand() / (double)(RAND_MAX - 1); array[i] = r; } } int main() { int n=VAL_N, diag=VAL_D; int i, j, iteration=0; double a[n][n]; double x[n], x_courant[n], b[n]; double norme; struct timeval t_elapsed_0, t_elapsed_1; double t_elapsed; clock_t t_cpu_0, t_cpu_1; double t_cpu; #ifdef _OPENMP int nb_taches; #pragma omp parallel { nb_taches = omp_get_num_threads(); } fprintf(stdout, "\n\n Execution jacobi en parallele avec %d threads\n", nb_taches); #endif // _OPENMP // Initialisation de la matrice et du second membre srand(421); // pour des resultats reproductibles random_number(&a[0][0], n*n); random_number(&b[0], n); // On muscle la diagonale principale de la matrice for(int i=0; i<n; i++) { a[i][i] += diag; } // Solution initiale for(int i=0; i<n; i++) { x[i] = 1.0; } printf("double size: %zu bytes\n", sizeof(double)); printf("Address of a: %p\n", (void*)a); // Temps CPU de calcul initial. t_cpu_0 = clock(); // Temps elapsed de reference. gettimeofday(&t_elapsed_0, NULL); // Resolution par la methode de Jacobi while(1) { iteration++; #pragma omp parallel for schedule(runtime) for(int i=0; i<n; i++) { x_courant[i] = 0; for(j=0; j<i; j++) { x_courant[i] += a[j][i]*x[j]; } for(j=i+1; j<n; j++) { x_courant[i] += a[j][i]*x[j]; } x_courant[i] = (b[i] - x_courant[i])/a[i][i]; } // Test de convergence { double absmax = 0; for(int i=0; i<n; i++) { double curr = fabs(x[i] - x_courant[i]); if (curr > absmax) absmax = curr; } norme = absmax / n; } if( (norme <= DBL_EPSILON) || (iteration >= n) ) break; // Copie de x_courant dans x memcpy(x, x_courant, n*sizeof(double)); } // Temps elapsed final gettimeofday(&t_elapsed_1, NULL); t_elapsed = (t_elapsed_1.tv_sec - t_elapsed_0.tv_sec) + (t_elapsed_1.tv_usec - t_elapsed_0.tv_usec) / (double)1000000; // Temps CPU de calcul final t_cpu_1 = clock(); t_cpu = (t_cpu_1 - t_cpu_0) / (double)CLOCKS_PER_SEC; // Impression du resultat fprintf(stdout, "\n\n" " Taille du systeme : %5d\n" " Iterations : %4d\n" " Norme : %10.3E\n" " Temps elapsed : %10.3E sec.\n" " Temps CPU : %10.3E sec.\n", n, iteration, norme, t_elapsed, t_cpu ); return EXIT_SUCCESS; }
编译与运行命令
编译命令:
gcc -Xclang -fopenmp -I/opt/homebrew/opt/libomp/include/ -L/opt/homebrew/opt/libomp/lib/ -lomp jacobi.c -o multi
调整栈大小并运行:
ulimit -s 16384 export OMP_SCHEDULE="STATIC,10" OMP_NUM_THREADS=2 ./multi
调试信息
运行时触发内存访问错误,lldb输出如下:
Execution jacobi en parallele avec 2 threads double size: 8 bytes Address of a: 0x16f2fdad0 Process 5813 stopped * thread #1, queue = 'com.apple.main-thread', stop reason = EXC_BAD_ACCESS (code=2, address=0x16fe00de8) frame #0: 0x0000000100003bc8 multi`.omp_outlined._debug__.3(.global_tid.=0x000000016f2f660c, .bound_tid.=0x000000016f2f6608, n=0x000000016fdfef10, vla=1201, x_courant=0x000000016f2f8fb0, j=0x000000016fdfef04, vla=1201, vla=1201, a=0x000000016f2fdad0, vla=1201, x=0x000000016f2fb540, vla=1201, b=0x000000016f2f6a20) at jacobi.c:91:24 88 for(int i=0; i<n; i++) { 89 x_courant[i] = 0; 90 for(j=0; j<i; j++) { -> 91 x_courant[i] += a[j][i]*x[j]; 92 } 93 for(j=i+1; j<n; j++) { 94 x_courant[i] += a[j][i]*x[j]; (lldb) fr v j (int &) j = 0x000000016fdfef04 (&j = 1011) (lldb) fr v i (int) i = 0
a为1201×1201的double数组,地址范围为0x16f2fdad0至0x16fdfedd7,但程序尝试访问0x16fe00de8,超出合法范围。
排查思路与解决方案
1. 数据竞争导致变量值混乱
代码中j变量在main函数顶部声明,属于全局共享变量。在OpenMP并行循环中,多个线程同时读写j,引发数据竞争,导致j的值被意外篡改(如调试中i=0时j=1011,违背循环逻辑),进而导致非法内存访问。
修复方案:将j声明为线程私有,有两种方式:
- 在并行循环的内层循环中直接声明
j,让每个线程拥有独立的j副本:#pragma omp parallel for schedule(runtime) for(int i=0; i<n; i++) { x_courant[i] = 0; for(int j=0; j<i; j++) { // 每个线程私有j x_courant[i] += a[j][i]*x[j]; } for(int j=i+1; j<n; j++) { x_courant[i] += a[j][i]*x[j]; } x_courant[i] = (b[i] - x_courant[i])/a[i][i]; } - 或通过OpenMP的
private子句显式声明j为私有:#pragma omp parallel for schedule(runtime) private(j) for(int i=0; i<n; i++) { x_courant[i] = 0; for(j=0; j<i; j++) { x_courant[i] += a[j][i]*x[j]; } for(j=i+1; j<n; j++) { x_courant[i] += a[j][i]*x[j]; } x_courant[i] = (b[i] - x_courant[i])/a[i][i]; }
2. 栈内存不足(VLA风险)
代码中使用变长数组(VLA)double a[n][n]在栈上分配内存,尽管调整了主线程栈大小,但OpenMP线程的栈空间默认较小,且Apple Silicon平台的栈布局可能导致线程访问主线程栈时出现溢出。此外,VLA的实现依赖编译器,在多线程环境下可能存在兼容性问题。
修复方案:改用堆内存分配数组,避免栈溢出:
// 替换原VLA声明 double **a = malloc(n * sizeof(double *)); for (int k=0; k<n; k++) { a[k] = malloc(n * sizeof(double)); } double *x = malloc(n * sizeof(double)); double *x_courant = malloc(n * sizeof(double)); double *b = malloc(n * sizeof(double)); // 程序结束后释放内存 for (int k=0; k<n; k++) { free(a[k]); } free(a); free(x); free(x_courant); free(b);
更高效的方式是使用一维数组模拟二维数组,减少内存分配次数:
double *a = malloc(n * n * sizeof(double)); // 访问a[j][i]改为a[j * n + i]
验证
修复后重新编译运行,即可避免Bus Error,正常执行多线程加速的Jacobi算法。
内容的提问来源于stack exchange,提问作者CBld
相关产品推荐
相关产品推荐

