OpenMP并行for循环显示性能提升很少

时间:2013-03-17 16:15:46

标签: c ubuntu concurrency parallel-processing openmp

我正在学习如何在C中使用OpenMP,作为一个HelloWorld练习,我正在编写一个计算素数的程序。然后我将其并行化如下:

int numprimes = 0;
#pragma omp parallel for reduction (+:numprimes)
for (i = 1; i <= n; i++)
{
    if (is_prime(i) == true)
        numprimes ++;
}

我使用gcc -g -Wall -fopenmp -o primes primes.c -lm-lm为我正在使用的math.h函数编译此代码。然后我在Intel® Core™2 Duo CPU E8400 @ 3.00GHz × 2上运行此代码,正如预期的那样,性能优于串行程序。

然而,当我尝试在功能更强大的机器上运行它时,问题就出现了。 (我还尝试手动设置要与num_threads一起使用的线程数,但这并没有改变任何东西。)计算所有素数到10 000 000给我以下时间(使用{{1 }}):

8核机器:

time

双核机器:

real    0m8.230s
user    0m50.425s
sys     0m0.004s

这种模式继续用于计算更多质数,具有更多核心的机器显示出轻微的性能提升,但没有像我预期的那样有更多核心可用。 (我希望核心数量增加4倍,这意味着运行时间减少了近4倍?)

将素数计算到real 0m10.846s user 0m17.233s sys 0m0.004s

8核机器:

50 000 000

双核机器:

real    1m29.056s
user    8m11.695s
sys     0m0.017s

如果有人能为我澄清这一点,我们将不胜感激。

修改

这是我的主要检查功能。

real    1m51.119s
user    2m50.519s
sys     0m0.060s

3 个答案:

答案 0 :(得分:6)

这种表现正在发生,因为:

  1. is_prime(i)需要的时间越长i越高,
  2. 您的OpenMP实现默认情况下对parallel for构造使用静态调度而不使用schedule子句,即它将for循环切换为相等大小的连续块。
  3. 换句话说,编号最高的线程正在执行所有最难的操作。

    使用schedule子句显式选择更合适的调度类型允许您公平地在线程之间划分工作。

    此版本将更好地划分工作:

    int numprimes = 0;
    #pragma omp parallel for schedule(dynamic, 1) reduction(+:numprimes) 
    for (i = 1; i <= n; i++)
    {
        if (is_prime(i) == true)
            numprimes ++;
    }
    

    有关计划语法的信息可通过MSDNWikipedia获得。

    schedule(dynamic, 1)可能不是最佳的,因为High Performance Mark在他的回答中指出。在此OpenMP wihtepaper中有一个关于调度粒度的更深入的讨论。

    还要感谢Jens GustedtMahmoud Fayez为此答案做出贡献。

答案 1 :(得分:3)

正如@naroom建议的那样,你的程序显然缩放的原因是每次调用is_prime函数的运行时间的可变性。运行时间不会随着i的值而增加。您的代码显示,一旦找到i的第一个因子,测试就会终止,因此最长的运行时间将适用于具有较少(和较大)因子的数字,包括素数本身。

正如您已经被告知的那样,并行化的默认计划将把主循环的迭代分成一个块一次分配给可用的线程。对于要测试5*10^7个整数和要使用的8个内核的情况,第一个线程将获得要测试的整数1..6250000,第二个线程将获得6250001..12500000,依此类推。这将导致线程之间的负载严重不平衡,因为当然,素数不是均匀分布的。

您应该尝试动态调度,而不是使用默认计划。以下语句告诉运行时将主循环m迭代的迭代分别包含在计算中的线程中:

#pragma omp parallel for schedule(dynamic,m)

一旦线程完成了m次迭代,它将获得更多m个工作。您可以找到sweet spot的{​​{1}}。太小,你的计算将由运行时间在迭代中进行的工作占主导地位,太大而你的计算将恢复到你已经看到的不平衡负载。

请记住,通过所有这些,您将学习关于并行计算的成本和收益的一些有用的经验教训。

答案 2 :(得分:1)

我认为您的代码需要使用动态,因此每个线程都可以使用不同的迭代次数,因为您的迭代具有不同的工作负载,因此当前代码是平衡的,这对您的情况无济于事请试试:

int numprimes = 0;
#pragma omp parallel for reduction (+:numprimes) schedule(dynamic,1)
for (i = 1; i <= n; i++){
    if (is_prime(i) == true)
    ++numprimes;
}