两个连续的OpenMP并行区域彼此放慢速度

时间:2018-10-15 14:54:25

标签: c++ openmp

我希望能够并行运行两个函数,并使用OpenMP并行区域来解决此问题。但是这样做会使我的代码变慢得多。经过一番测试后,我注意到并行区域不是问题,它确实比以前快了,但是我代码的另一部分(我没有更改)变得慢了50倍。

一个小时后的测试中,我注意到第二个区域中的omp parallel for导致了此现象。似乎两个连续的并行区域会彼此放慢速度。我编写了一个小型测试程序来验证这一点:

#include <iostream>
#include <chrono>
#include <thread>
#include <omp.h>
#include <vector>
#include <cmath>

using namespace std::chrono_literals;

inline void func1()
{
    // simulate a cpu function that takes 5ms
    std::this_thread::sleep_for(5ms);
}

inline void func2()
{
    // simulate a cpu function that takes 6ms
    std::this_thread::sleep_for(6ms);
}

int main()
{
    // initialize some vectors to test an omp parallel for pragma
    std::vector<float> vec1(10000);
    std::vector<float> vec2(10000);
    std::vector<float> vec3(10000);
    for(int i = 0; i < 10000; i++)
    {
        vec1[i] = (i+1)/1000.0;
        vec2[i] = i;
        vec3[i] = 0;
    }

    // timings taken via std::chrono
    typedef std::chrono::time_point<std::chrono::high_resolution_clock>
                                          time_point;
    typedef std::chrono::duration<double, std::milli> duration;

    // first block
    std::cout << "serial wait, serial loop" << std::endl;
    for(int k = 0; k < 20; k++)
    {

        time_point start = std::chrono::high_resolution_clock::now();

        func1();
        func2();

        duration time1 = std::chrono::high_resolution_clock::now() - start;
        start = std::chrono::high_resolution_clock::now();

        for(int i = 0; i < 10000; i++)
        {
            vec3[i] = sqrt(sin(pow(vec1[i],vec2[i]))*sin(0.5*pow(vec1[i],vec2[i])));
        }

        duration time2 = std::chrono::high_resolution_clock::now() - start;

        std::cout << k << " " << time1.count() << " " << time2.count() << std::endl;
    }

    // second block
    std::cout << "parallel wait, serial loop" << std::endl;
    for(int k = 0; k < 20; k++)
    {

        time_point start = std::chrono::high_resolution_clock::now();

        #pragma omp parallel num_threads(2)
        {
            if(omp_get_thread_num() == 0)
            {
                func1();
            }
            else
            {
                func2();
            }
        }

        duration time1 = std::chrono::high_resolution_clock::now() - start;
        start = std::chrono::high_resolution_clock::now();

        for(int i = 0; i < 10000; i++)
        {
            vec3[i] = sqrt(sin(pow(vec1[i],vec2[i]))*sin(0.5*pow(vec1[i],vec2[i])));
        }

        duration time2 = std::chrono::high_resolution_clock::now() - start;

        std::cout << k << " " << time1.count() << " " << time2.count() << std::endl;
    }

    // third block
    std::cout << "serial wait, parallel loop" << std::endl;
    for(int k = 0; k < 20; k++)
    {

        time_point start = std::chrono::high_resolution_clock::now();

        func1();
        func2();

        duration time1 = std::chrono::high_resolution_clock::now() - start;
        start = std::chrono::high_resolution_clock::now();

        #pragma omp parallel for
        for(int i = 0; i < 10000; i++)
        {
            vec3[i] = sqrt(sin(pow(vec1[i],vec2[i]))*sin(0.5*pow(vec1[i],vec2[i])));
        }

        duration time2 = std::chrono::high_resolution_clock::now() - start;

        std::cout << k << " " << time1.count() << " " << time2.count() << std::endl;
    }

    // fourth block <-- weird behavior
    std::cout << "parallel wait, parallel loop" << std::endl;
    for(int k = 0; k < 20; k++)
    {

        time_point start = std::chrono::high_resolution_clock::now();

        #pragma omp parallel num_threads(2)
        {
            if(omp_get_thread_num() == 0)
            {
                func1();
            }
            else
            {
                func2();
            }
        }

        duration time1 = std::chrono::high_resolution_clock::now() - start;
        start = std::chrono::high_resolution_clock::now();

        #pragma omp parallel for
        for(int i = 0; i < 10000; i++)
        {
            vec3[i] = sqrt(sin(pow(vec1[i],vec2[i]))*sin(0.5*pow(vec1[i],vec2[i])));
        }

        duration time2 = std::chrono::high_resolution_clock::now() - start;

        std::cout << k << " " << time1.count() << " " << time2.count() << std::endl;
    }    
}

如果运行此命令,我将从控制台获取:

serial wait, serial loop
0 11.8541 3.23881
1 11.4908 3.18409
2 11.8729 3.12847
3 11.6656 3.19606
4 11.8484 3.14534
5 11.863 3.20833
6 11.8331 3.13007
7 11.8351 3.20697
8 11.8337 3.14418
9 11.8361 3.21004
10 11.833 3.12995
11 11.8349 3.14703
12 11.8341 3.1457
13 11.8324 3.14509
14 11.8339 3.12721
15 11.8382 3.14233
16 11.8368 3.14509
17 11.8335 3.14625
18 11.832 3.15115
19 11.8341 3.14499
parallel wait, serial loop
0 6.59906 3.14325
1 6.42459 3.14945
2 6.42381 3.13722
3 6.43271 3.19783
4 6.42408 3.12781
5 6.42404 3.14482
6 6.42534 3.20757
7 6.42392 3.14144
8 6.425 3.14805
9 6.42331 3.1312
10 6.4228 3.14783
11 6.42556 3.15106
12 6.42523 3.14562
13 6.42523 3.14605
14 6.42399 3.12967
15 6.42273 3.14699
16 6.42276 3.15026
17 6.42471 3.14164
18 6.42302 3.14701
19 6.42483 3.19149
serial wait, parallel loop
0 11.8319 4.51681
1 11.4756 0.928738
2 11.1129 0.221045
3 11.1075 0.220827
4 11.1081 0.220197
5 11.1065 0.218774
6 11.1059 0.218329
7 11.1658 0.218804
8 11.1063 0.218056
9 11.107 0.21789
10 11.108 0.218605
11 11.1059 0.217867
12 11.1218 0.218198
13 11.1059 0.217666
14 11.1056 0.219443
15 11.1064 0.217653
16 11.106 0.21729
17 11.1064 0.217565
18 11.1085 0.217965
19 11.1056 0.21735
parallel wait, parallel loop
0 6.41053 6.92563
1 6.06954 4.88433
2 6.4147 0.948097
3 6.41245 5.95226
4 6.41169 4.20988
5 6.41415 3.34145
6 6.41655 4.26902
7 6.41321 1.80355
8 6.41332 1.53747
9 6.41386 1.5394
10 6.06738 1.88866
11 6.41286 1.531
12 6.4133 1.53643
13 6.41356 6.40577
14 6.70144 3.48257
15 6.41551 3.60291
16 6.39516 4.44704
17 6.92893 0.981749
18 6.41533 1.50914
19 6.41685 8.36792

前三个输出块与预期的一样:串行等待5和6 ms需要大约11 ms。向量计算3.1ms。如果并行执行两次等待,则花费的时间与两者中最慢的时间(6毫秒)一样长。并且(12线程)并行for循环大约需要0.22毫秒。

但是我连续使用两个平行区域的第四块似乎很奇怪。我的循环时间几乎是随机的,并且比第三个程序段要慢得多(即使在最好的情况下)。它反映了我的非测试代码中的上述行为。

谁能解释我,为什么会这样以及如何解决呢?

我使用以下代码进行编译

g++ main.cpp -O3 -fompenmp

我也在Windows pc上测试了此代码,并看到了相同的行为。

如果我删除第一个并行区域中的num_threads(2),问题就不会那么严重了,但仍然很明显。 (但是num_threads(2)应该只影响第一个区域,不是吗?)

谢谢。

2 个答案:

答案 0 :(得分:0)

我在重现此问题时遇到了一些麻烦,但是我相信这是正在发生的事情。

Libgomp(gcc的OpenMP运行时)使用线程池。如果只有连续的parallel循环,则不会产生新的线程。但是,如果您交替使用parallel for(使用12个线程)和parallel num_threads(2),则libgomp决定将线程池缩小至2 ,然后再次将其增大到12

我通过在并行循环中打印OpenMP线程#1 /#2的gettid()来验证这一点。尽管#1保持为pid,但#2每次迭代都会获得一个新值。如您所知,您可以轻松解决此问题。正如肖恩(Shawn)所说,parallel sections还是比较惯用的解决方案。

至于其余的差异,我目前无法重现-结果非常嘈杂。您可以尝试pinning the threads to CPUs。否则,如果涉及到如此短的时间段,则可能必须通过将并行区域移出调用两个区域的代码之外来保持并行区域活动,同时确保正确的控制流。

答案 1 :(得分:0)

正如Zulan指出的那样,“解决方案”是使平行区域保持活动状态:

// fifth block <-- "solution"
std::cout << "parallel wait, parallel loop" << std::endl;
for(int k = 0; k < 20; k++)
{

    time_point start = std::chrono::high_resolution_clock::now();

    #pragma omp parallel
    {
        if(omp_get_thread_num() == 0)
        {
            func1();
        }
        else if(omp_get_thread_num() == 1)
        {
            func2();
        }

        #pragma omp for
        for(int i = 0; i < 10000; i++)
        {
            vec3[i] = sqrt(sin(pow(vec1[i],vec2[i]))*sin(0.5*pow(vec1[i],vec2[i])));
        }
    }
    duration time1 = std::chrono::high_resolution_clock::now() - start;

    std::cout << k << " " << time1.count() << std::endl;
} 

这样,结果甚至比每次迭代中并行等待(第二个块)和并行循环(第三个块)时序(〜6.3ms)之和还要快。不幸的是,该解决方案在我的实际应用程序中不起作用,但是我将针对该问题开始另一个主题。

我注意到,仅在使用超线程的情况下才会出现此问题。我的CPU有6个内核,使用超线程支持12个线程。如果我以OMP_NUM_THREADS = 6运行测试代码,那么第4个块中的奇怪行为就消失了。