openmp的嵌套for循环中的性能改进失败

时间:2019-04-11 14:11:02

标签: c openmp

我正在做Eratosthenes Sieve算法,以查找n之前的质数。想法是划定素数的所有倍数。但是,随着线程数量的增加,它没有实现性能提升。

使用100个线程需要0.009秒,使用1个线程需要0.006秒才能找到100000之前的质数。

#pragma omp parallel num_threads(t)
{
    #pragma omp for
    for (int index = 2; index <= N; index++) {
        list_of_nums[index - 2] = index;
    }

    #pragma omp for
    for (int loop_index = 2; loop_index <= num_to_stop; loop_index++) {
        if (list_of_nums[loop_index - 2] != -1) {
            for (int mark_index = loop_index - 2; mark_index < N - 1; mark_index += loop_index) {
                if (mark_index != loop_index - 2) {
                    if (list_of_nums[mark_index] % loop_index == 0) {
                        list_of_nums[mark_index] = -1;
                    }
                }
            }
        }
    }
}

1 个答案:

答案 0 :(得分:5)

首先,尽管有其他原因,但不能保证并行化可以提高程序速度。管理多个线程会增加计算的开销,并且可能无法同时执行多个计算而获得加速。

第二,提速范围受可实现的并发量限制。特别是,对于没有阻塞操作的计算,与拥有独立的执行引擎(大约是内核)相比,添加更多线程不会带来改善。

但是,第三,在这里我将重点解决这个问题的其余部分,即Eratosthenes的Sieve具有数据依赖性,这使其不适合并行化。您甚至从并行版本中获得正确的结果都来自于实现的特定特性。问题集中在这里:

        if (list_of_nums[loop_index - 2] != -1) {

正在检查是否已经确定loop_index是复合的,以便跳过多余的筛分倍数。那里的关键字是“已经”。如果loop_index 复合的,并且分配了与当前线程不同的线程来测试其主要因子,那么您将不确定loop_index已经被标记了< / em>复合。

如果此时选择质数并将它们存储在一个单独的列表中(与SofE实现常见的那样),您会遇到麻烦。另一方面,您的特定实现很可能会做很多不必要的工作,以筛选出复合材料的倍数。因此,您不仅会因为管理多个线程而产生开销,而且可能会做更多的总工作。从这个意义上说,它并不是Eratosthenes的筛子。

可以写一个并行版本的SofE来正确地尊重其数据依赖性,尽管我不确定OpenMP是否足够丰富来描述一个。我已经用另一种语言完成了它,并且确实表现出了一些加速。但是适当地尊重依赖关系会极大地限制可用的并发数量(并增加更多开销),因此加速过程相当乏味。

更新:可能的替代方案

通过测量,您知道并行方法的性能要比基础串行方法差。您可以尝试调整参数,例如使用的确切线程数,但是最好朝另一个方向进行调整。有希望的替代方案包括:

  • 只需使用算法的串行版本即可。根据您的测量,这可以将运行时间减少33%,这一点也不差。

  • 预先计算您的筛子/素数列表,而不是即时对其进行计算。这样,计算性能就不会成为大型服务性能的一个因素。

  • 通过并行标记多个小质数的倍数并接受所涉及的冗余来预先筛分筛子,然后从那里运行标准的串行SofE。您可能希望通过针对不同选择进行适当的性能度量来调整预播过程中要使用的已知素数和线程数。

此外,您可以实施一些微优化来(甚至)从串行版本中获得一点加速。这与问题是相切的,因此我在这里不做具体说明,但是您应该在网上轻松地找到示例。