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

递归函数并行化问题:OpenMP优化find_words函数遇性能异常

递归回溯单词查找程序的OpenMP并行化问题

问题描述

我实现了一个基于递归回溯的字母网格单词查找程序,串行运行时约40秒能得到正确结果,但尝试用OpenMP并行化后,程序运行时间过长疑似死循环。

递归函数代码:

void find_words(char grid[ROWS][COLS], int row, int col, char prefix[MAX_WORD_LENGTH], int visited[ROWS][COLS], node_t** table) {
    // Mark current cell as visited
    visited[row][col] = 1;

    // Append current letter to prefix
    char current_word[MAX_WORD_LENGTH];
    strncpy(current_word, prefix, MAX_WORD_LENGTH);
    strncat(current_word, &grid[row][col], 1);

    // Check if prefix is a word in the dictionary
    if (ht_search(table, current_word)) 
    {
         int is_Exist=0;
         int i;
       
        for (i=0;i<found_words_counter;i++)
         {
            if(strcmp(found_words[i],current_word)==0)
            {
               is_Exist=1;
               break;
            }
         }  
         if(!is_Exist)  
            {   
              strncpy(found_words[found_words_counter++],current_word,MAX_WORD_LENGTH);
            }
       }
   
    // Check for words that can be formed by extending the prefix
    int i, j;
    #pragma omp parallel for private(i, j) shared(grid, visited, table, current_word)
      for (i = -1; i <= 1; i++) {
        for (j = -1; j <= 1; j++) {
            if (row+i >= 0 && row+i < ROWS && col+j >= 0 && col+j < COLS && !visited[row+i][col+j] && !(i == 0 && j == 0)) {
                find_words(grid, row+i, col+j, current_word, visited, table);
            }
        }
    }

    // Mark current cell as unvisited
    visited[row][col] = 0;
}

主函数调用代码:

#pragma omp parallel for private(i, j) shared(grid, visited, table, prefix)
for (i = 0; i < ROWS; i++) {
    for (j = 0; j < COLS; j++) {
        find_words(grid, i, j, prefix, visited, table);
    }
}

程序功能是从ROWS×COLS的字母网格中,通过回溯拼接字母前缀,检查前缀是否存在于含50k单词的哈希表中,若存在则加入结果数组。请问递归函数能否并行化?如何正确修改实现并行化?


解决方案

递归回溯类程序可以并行化,但你的代码存在三个核心问题导致死循环和性能崩溃,以下是修复方案:

1. 消除共享visited数组的线程冲突

你在主函数中让所有线程共享同一个visited数组,多个线程同时修改和回溯标记时会彻底混乱:比如线程A标记某单元格为已访问,线程B未感知到该标记就重复访问,或者线程A回溯时错误清除线程B的标记,直接引发无限递归或死循环。

修复:每个线程的起始单元格迭代(每个i,j)必须拥有独立的visited数组副本,主函数并行循环修改如下:

#pragma omp parallel for collapse(2) private(i, j, visited, prefix)
for (i = 0; i < ROWS; i++) {
    for (j = 0; j < COLS; j++) {
        // 每个迭代初始化独立的visited数组和空前缀
        int visited[ROWS][COLS] = {0};
        char prefix[MAX_WORD_LENGTH] = "";
        find_words(grid, i, j, prefix, visited, table);
    }
}

collapse(2)用于并行化二维循环,提升线程利用率;每个迭代的visited和prefix都是私有变量,线程间完全隔离。

2. 修复全局结果数组的竞态条件

found_words和found_words_counter是全局共享变量,多个线程同时读写会导致数据竞争:比如两个线程同时判断某个单词未存在,然后同时写入数组导致内容覆盖,或者计数器递增混乱引发数组越界,这也会导致程序异常或卡死。

修复:用OpenMP临界区保护对全局结果的操作,缩小临界区范围以减少性能损失:

if (ht_search(table, current_word)) 
{
    #pragma omp critical
    {
        int is_Exist = 0;
        for (int k = 0; k < found_words_counter; k++)
        {
            if(strcmp(found_words[k], current_word) == 0)
            {
                is_Exist = 1;
                break;
            }
        }  
        if(!is_Exist)  
        {   
            strncpy(found_words[found_words_counter++], current_word, MAX_WORD_LENGTH);
        }
    }
}

若担心临界区开销过大,可改为每个线程先收集自身的结果列表,最后再合并到全局数组,完全避免竞争。

3. 删除递归内部的并行循环

你在递归函数中嵌套了#pragma omp parallel for,这会导致每次递归都创建新的线程池,线程数量指数级增长,系统资源直接耗尽,程序彻底卡死。

修复:去掉递归内部的并行指令,改为串行遍历邻接单元格:

// 去掉#pragma omp parallel for,改为串行循环
int i, j;
for (i = -1; i <= 1; i++) {
    for (j = -1; j <= 1; j++) {
        if (row+i >= 0 && row+i < ROWS && col+j >= 0 && col+j < COLS && !visited[row+i][col+j] && !(i == 0 && j == 0)) {
            find_words(grid, row+i, col+j, current_word, visited, table);
        }
    }
}

修改后的完整递归函数

void find_words(char grid[ROWS][COLS], int row, int col, char prefix[MAX_WORD_LENGTH], int visited[ROWS][COLS], node_t** table) {
    // Mark current cell as visited
    visited[row][col] = 1;

    // Append current letter to prefix
    char current_word[MAX_WORD_LENGTH];
    strncpy(current_word, prefix, MAX_WORD_LENGTH);
    strncat(current_word, &grid[row][col], 1);

    // Check if prefix is a word in the dictionary
    if (ht_search(table, current_word)) 
    {
        #pragma omp critical
        {
            int is_Exist = 0;
            for (int k = 0; k < found_words_counter; k++)
            {
                if(strcmp(found_words[k], current_word) == 0)
                {
                    is_Exist = 1;
                    break;
                }
            }  
            if(!is_Exist)  
            {   
                strncpy(found_words[found_words_counter++], current_word, MAX_WORD_LENGTH);
            }
        }
    }

    // Check for words that can be formed by extending the prefix
    int i, j;
    for (i = -1; i <= 1; i++) {
        for (j = -1; j <= 1; j++) {
            if (row+i >= 0 && row+i < ROWS && col+j >= 0 && col+j < COLS && !visited[row+i][col+j] && !(i == 0 && j == 0)) {
                find_words(grid, row+i, col+j, current_word, visited, table);
            }
        }
    }

    // Mark current cell as unvisited
    visited[row][col] = 0;
}

内容的提问来源于stack exchange,提问作者blake

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.22 10:15:07