【问题标题】:OpenMP behavior - Nested MultithreadingOpenMP 行为 - 嵌套多线程
【发布时间】:2020-04-16 18:18:18
【问题描述】:

我的问题与嵌套并行和 OpenMP 有关。让我们从下面的单线程代码sn-p开始:

void performAnotherTask() {
    // DO something here
}

void performTask() {
    // Do other stuff here
    for (size_t i = 0; i < 100; ++i) {
        performAnotherTask();
    }
}


int main() {
    for (size_t i = 0; i < 100; ++i) {
        performTask();
    }   

    return 0;
}

现在假设我们要使用 OpenMP 并行调用 performAnotherTask

所以我们得到以下代码:

void performAnotherTask() {
    // DO something here
}

void performTask() {
    // Do other stuff here
#pragma omp parallel for
    for (size_t i = 0; i < 100; ++i) {
        performAnotherTask();
    }
}


int main() {
    for (size_t i = 0; i < 100; ++i) {
        performTask();
    }   

    return 0;
}

我的理解是,对performAnotherTask 的调用将并行执行,默认情况下,openMP 会尝试使用您机器上的所有可用线程(也许这个假设是不正确的)。

假设我们现在还想并行化对performTask 的调用,这样我们就可以得到以下代码:

void performAnotherTask() {
    // DO something here
}

void performTask() {
    // Do other stuff here
#pragma omp parallel for
    for (size_t i = 0; i < 100; ++i) {
        performAnotherTask();
    }
}


int main() {
#pragma omp parallel for
    for (size_t i = 0; i < 100; ++i) {
        performTask();
    }   

    return 0;
}

这将如何运作?两个 for 循环仍然是多线程的吗?我们能说一下每个循环将使用的线程数吗?有没有办法强制内部 for 循环(在performTask 内)仅使用单个线程,而外部 for 循环使用所有可用线程?

【问题讨论】:

    标签: c++ multithreading openmp


    【解决方案1】:

    在您的最后一个示例中,执行行为取决于一些环境设置。

    首先,OpenMP 确实支持此类模式,但默认情况下禁用嵌套并行区域中的并行执行。要启用它,您必须在代码中设置OMP_NESTED=true 或调用omp_set_nested(1)。然后启用对嵌套并行执行的支持。

    void performAnotherTask() {
        // DO something here
    }
    
    void performTask() {
        // Do other stuff here
    #pragma omp parallel for
        for (size_t i = 0; i < 100; ++i) {
            performAnotherTask();
        }
    }
    
    
    int main() {
        omp_set_nested(1);
    #pragma omp parallel for
        for (size_t i = 0; i < 100; ++i) {
            performTask();
        }   
    
        return 0;
    }
    

    其次,当 OpenMP 到达外部parallel 区域时,它可能会抓取所有可用内核并假设它可以在它们上执行线程,因此您可能希望减少外部级别的线程数,以便一些核心可用于嵌套区域。比如说,如果你有 32 个核心,你可以这样做:

    void performAnotherTask() {
        // DO something here
    }
    
    void performTask() {
        // Do other stuff here
    #pragma omp parallel for num_threads(8)
        for (size_t i = 0; i < 100; ++i) {
            performAnotherTask();
        }
    }
    
    
    int main() {
        omp_set_nested(1);
    #pragma omp parallel for num_threads(4)
        for (size_t i = 0; i < 100; ++i) {
            performTask();
        }   
    
        return 0;
    }
    

    外部并行区域将使用 4 个线程执行,每个线程将使用 8 个线程执行内部区域。请注意,4 个外部线程中的每一个都将是四个同时执行的嵌套并行区域的主线程之一。如果您想更加灵活,您可以使用环境变量OMP_NUM_THREADS 为每个级别注入要使用的线程数。如果您将其设置为OMP_NUM_THREADS=4,8,您将获得与上面我发布的第一个代码 sn-p 相同的行为。

    编码模式的问题在于,您需要小心平衡每个级别,以免系统过载或嵌套并行区域之间出现负载不平衡。另一种解决方案是改用 OpenMP 任务:

    void performAnotherTask() {
        // DO something here
    }
    
    void performTask() {
        // Do other stuff here
    #pragma omp taskloop
        for (size_t i = 0; i < 100; ++i) {
            performAnotherTask();
        }
    }
    
    
    int main() {
        omp_set_nested(1);
    #pragma omp parallel 
    #pragma omp single
    #pragma omp taskloop
        for (size_t i = 0; i < 100; ++i) {
            performTask();
        }   
    
        return 0;
    }
    

    这里每个taskloop 构造都将生成OpenMP 任务,这些任务计划在由代码中的单个parallel 区域创建的线程上执行。需要注意的是,任务的行为本质上是动态的,因此您可能会丢失局部性属性,因为您不知道任务将在系统中的确切位置执行。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多