You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.29 13:44:57