微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

OpenMP Sudoku 求解器 - 使用 openMP 和/或 MPI 并行化算法

如何解决OpenMP Sudoku 求解器 - 使用 openMP 和/或 MPI 并行化算法

我在 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 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,num)) {
            int val = 0;
            #pragma omp task firstprivate(grid,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 将算法与多处理并行化,但我不知道从哪里开始。我很感激任何帮助或资源为我指明了正确的方向。

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

解决方法

在您的 solve 调用中有一个循环(有效地)所有仍可放置的数字。在每次迭代中,您都会创建一个任务,然后等待它。这意味着你是完全连续的。

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

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

OpenMP 4 助您一臂之力:您可以cancel 一个任务组,有效地摆脱它。

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

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

,

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

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

要减少创建的任务数量(这也意味着增加任务的工作量),您可以在 final 指令中使用 if#pragma omp task 子句。我引入了一个新变量 level 来跟踪递归深度。在我的计算机上(以及在 Godbolt 上)使用 final(level>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

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。