【问题标题】:OpenMP Sudoku solver - Parallelize algorithm with openMP and/or MPIOpenMP Sudoku 求解器 - 使用 openMP 和/或 MPI 并行化算法
【发布时间】:2021-09-20 00:51:51
【问题描述】:

我在 c 中创建了一个数独求解器,到目前为止它正在工作。
我正在读取一个 txt 文件并将其解析为一个 9x9 int 数组:int sudoku[9][9] 我实现了一个简单的蛮力回溯算法,我检查每个位置。如果我需要分配一个数字,我会遍历值 1 到 9 并检查它们是否有效。如果它们合适,我将移至下一个索引。
我需要并行化算法以使用 MPI 与多个处理器一起工作,但我真的不知道从哪里以及如何开始。

现在我首先尝试使用 OpenMP 任务并行执行。我想并行检查可能的数字,理想情况下为每个需要检查的数字创建一个任务。

我目前的做法:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <omp.h>
#include <time.h>
#include <unistd.h>

#define SIZE 9
#define UNASSIGNED 0

//  compile with 
//                              gcc -fopenmp <filename>.c -o <name>
//Optional set num of threads:  export OMP_NUM_THREADS=4
//                              ./<name>
clock_t start, end;

void print_grid(int grid[SIZE][SIZE]) {
    for (int row = 0; row < SIZE; row++) {
        for (int col = 0; col < SIZE; col++) {
            printf("%2d", grid[row][col]);
        }
        printf("\n");
    }
}

//https://stackoverflow.com/questions/1726302/removing-spaces-from-a-string-in-c
void remove_spaces(char* s) {
    const char* d = s;
    do {
        while (*d == ' ') {
            ++d;
        }
    } while (*s++ = *d++);
}

int is_exist_row(int grid[SIZE][SIZE], int row, int num){
    for (int col = 0; col < 9; col++) {
        if (grid[row][col] == num) {
            return 1;
        }
    }
    return 0;
}

int is_exist_col(int grid[SIZE][SIZE], int col, int num) {
    for (int row = 0; row < 9; row++) {
        if (grid[row][col] == num) {
            return 1;
        }
    }
    return 0;
}

int is_exist_box(int grid[SIZE][SIZE], int startRow, int startCol, int num) {
    for (int row = 0; row < 3; row++) {
        for (int col = 0; col < 3; col++) {
            if (grid[row + startRow][col + startCol] == num) {
                return 1;
            } 
        }
    }
    return 0;
}

int is_safe_num(int grid[SIZE][SIZE], int row, int col, int num) {
    return !is_exist_row(grid, row, num) 
            && !is_exist_col(grid, col, num) 
            && !is_exist_box(grid, row - (row % 3), col - (col %3), num);
}

int find_unassigned(int grid[SIZE][SIZE], int *row, int *col) {
    for (*row = 0; *row < SIZE; (*row)++) {
        for (*col = 0; *col < SIZE; (*col)++) {
            if (grid[*row][*col] == 0) {
                return 1;
            }
        }
    }
    return 0;
}


int solve(int grid[SIZE][SIZE]) {
    
    int row = 0;
    int col = 0;
    
    if (!find_unassigned(grid, &row, &col)){
        return 1;
    }
    
    for (int num = 1; num <= SIZE; num++ ) {        
        if (is_safe_num(grid, row, col, num)) {
            int val = 0;
            #pragma omp task firstprivate(grid, row, col,val,num)
            {
                int copy_grid[SIZE][SIZE];
                for (int row = 0; row < SIZE; row++) {
                    for (int col = 0; col < SIZE; col++) {                      
                        copy_grid[row][col] = grid[row][col];
                    }                   
                }
                
                copy_grid[row][col] = num;              
                val = solve(copy_grid);
                
                if(val) {
                    print_grid(copy_grid);
                    end = clock();
                    double time_spent = (double)(end - start) / CLOCKS_PER_SEC;  
                    printf("\nGelöst in %f s\n",time_spent);                    
                    exit(0);                    
                }
            }                       
            
            if (val) {
                return 1;
            }
            
            grid[row][col] = UNASSIGNED;
            #pragma omp taskwait
        }
    }
    
    return 0;
}

int main(int argc, char** argv) {
    
    int sudoku[SIZE][SIZE];
    
    FILE *file;
    char *line = NULL;
    size_t len = 0;
    ssize_t read;
    
    int i,j;
    i =0;
    file = fopen("Test_Sudoku_VeryDifficult.txt","r");
    if(file) {
        while(read = getline(&line, &len,file) != -1) {
            
            remove_spaces(line);                        
            
            for(j = 0; j < SIZE; j++) {
                sudoku[i][j] = (int)line[j] - '0';  //char to int           
            }   
            
            i++;
        }
        fclose(file);
        if (line) free(line);
        
        printf("Size: %d", SIZE);   
        printf("\n");
        
        start = clock();
        printf("Solving Sudoku: \n");
        print_grid(sudoku);
        printf("---------------------\n");
       #pragma omp parallel sections 
       {
           #pragma omp section
           {
               solve(sudoku);   
           }
       }
        exit(EXIT_SUCCESS);
    } else {
        exit(EXIT_FAILURE);
    }
}

我的灵感来自this stackoverflow post

我的数独文件是这样的

0 0 0 0 0 0 0 1 0 
4 0 0 0 0 0 0 0 0 
0 2 0 0 0 0 0 0 0 
0 0 0 0 5 0 4 0 7
0 0 8 0 0 0 3 0 0 
0 0 1 0 9 0 0 0 0 
3 0 0 4 0 0 2 0 0 
0 5 0 1 0 0 0 0 0 
0 0 0 8 0 6 0 0 0 

当我编译并执行它时,它可以工作,但任务不会并行执行。每个任务都等待他的子任务完成,然后再进行下一个。如果我删除#pragma omp taskwait,程序将无法正常工作并且输出错误。 我在打印结果时添加了exit(0),否则程序会继续探索其他路径。 我真的不知道我需要改变什么才能提高。

我还想通过 MPI 的多处理并行化算法,但我不知道从哪里开始。我很感激任何可以为我指明正确方向的帮助或资源。

在此先感谢您提供有关如何改进的任何帮助或建议。

【问题讨论】:

  • 你为什么使用#pragma omp section?这不是必需的,而且弊大于利。
  • #pragma omp parallel sections 是从初始线程启动 OpenMP 任务的惯用语之一,与 #pragma omp parallel master#pragma omp parallel masked#pragma omp single 紧跟在 #pragma omp parallel 之后非常相似。

标签: c mpi openmp openmpi sudoku


【解决方案1】:

在您的solve 电话中,有一个循环(有效地)所有仍然可以放置的号码。在每次迭代中,您创建一个任务,然后等待它。这意味着你是完全顺序的。

代替每个任务的wait,在循环之前放置taskgroup,以便迭代并行产生并作为一个组完成。

但是当你得到这个工作时,你会发现并行版本的运行时间比顺序版本要长得多,因为当你找到解决方案时,顺序版本会停止,而并行版本会遍历整个搜索空间。打破并行搜索很困难。无法使用 OpenMP 并行化 while 循环也是同样的问题。

OpenMP 4 进行救援:您可以cancel 一个任务组,有效地脱离它。

巧合的是,不久前我做了一个非常相似的编程练习: https://pages.tacc.utexas.edu/~eijkhout/pcse/html/omp-examples.html#Depth-firstsearch

使用 MPI 执行此操作更加困难,部分原因是同步成本非常高。 (事实上​​,您可能不得不将 sudoko 放大到 100x100 版本来克服开销。)最简单的解决方案可能是在顶层划分搜索空间。

【讨论】:

    【解决方案2】:

    如果您希望提高 openMP 程序的速度,您必须:

    1. #pragma omp taskwait 放置在for 循环之后而不是内部(@Victor 也建议和解释了)
    2. 减少生成的任务数量。您的程序遇到#pragma omp task 指令26590294 次。因此,单个任务的工作量太小,与任务的工作量相比,任务创建的开销变得很大。当然,OpenMP 运行时不会启动 2600 万个任务,它会将它们合并,但无论如何都会造成开销。

    要减少创建的任务数量(这也意味着增加任务的工作量),您可以在#pragma omp task 指令中使用finalif 子句。我引入了一个新变量level 来跟踪递归深度。在我的计算机(以及 Godbolt)上,使用 final(level&gt;1) 提供了最快的运行时间,但这取决于使用的线程数和实际解决的数独问题。所以修改后的程序是:

    int solve(int grid[SIZE][SIZE], int level) {                  
        ....
        for (int num = 1; num <= SIZE; num++ ) {   
                ....
                #pragma omp task firstprivate(grid,row,col,val,num,level) final(level>1)
                {           
                    .... 
                    val = solve(copy_grid, level+1);
                    .....
                }                                       
                ....                        
        }
        #pragma omp taskwait
        return 0;
    }
    

    main 中最好使用#pragma omp single nowait#pragma omp master(如前所述):

    #pragma omp parallel
    #pragma omp single nowait
    {
       solve(sudoku,1);   
    }
    

    Godbolt 链接是here

    【讨论】:

    • 我不确定我是否同意限制任务数量。我已经成功地产生了数百万个任务。现代 OMP 任务调度程序(意思是:不是 GCC)非常有效。但是,是的,我们在这里谈论的是非常小的负载。
    • @Victor,感谢您的评论。我刚刚还尝试在我的计算机上使用 clang++ (12.0.1 -O3 -fopenmp)(笔记本电脑上有 4 个线程)最终(级别> 1):0.44 秒,最终(级别> 2):1.07 秒最终(级别> 3) : 3.16 秒。单线程运行时间:2.2 s
    • 在另一台也使用 clang 的计算机(8 个线程)上,我得到了这些值,但我无法真正理解它们。所以运行时间:level>1: real 0m0.450s, real 0m0.406s, level>2: real 0m0.744s, real 0m0.759s, level>3: real 0m0.562s, real 0m0.574s, level>4: real 0m0.462s, real 0m0.483s, no final used: real 0m0.881s, real 0m0.883s, 单核: real 0m2.120s, real 0m2.143s
    猜你喜欢
    • 2022-01-19
    • 1970-01-01
    • 1970-01-01
    • 2019-11-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-08-10
    • 2018-09-29
    相关资源
    最近更新 更多