如何解决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 程序的速度,您必须:
- 将
#pragma omp taskwait
放在for
循环之后,而不是在里面(正如 @Victor 所建议和解释的) - 减少产生的任务数量。您的程序遇到
#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 举报,一经查实,本站将立刻删除。