为什么ubuntu 12.04下的OpenMP比串行版慢

时间:2014-04-20 16:00:33

标签: c++ ubuntu pthreads openmp

我已经阅读了有关此主题的其他一些问题。 但是,无论如何,他们并没有解决我的问题。

我编写了如下代码,我的pthread版本和omp版本都比串行版本慢。我很困惑。

在环境下编译:

Ubuntu 12.04 64bit 3.2.0-60-generic
g++ (Ubuntu 4.8.1-2ubuntu1~12.04) 4.8.1

CPU(s):                2
On-line CPU(s) list:   0,1
Thread(s) per core:    1
Vendor ID:             AuthenticAMD
CPU family:            18
Model:                 1
Stepping:              0
CPU MHz:               800.000
BogoMIPS:              3593.36
L1d cache:             64K
L1i cache:             64K
L2 cache:              512K
NUMA node0 CPU(s):     0,1

编译命令:

g++ -std=c++11 ./eg001.cpp -fopenmp

#include <cmath>
#include <cstdio>
#include <ctime>
#include <omp.h>
#include <pthread.h>

#define NUM_THREADS 5
const int sizen = 256000000;

struct Data {
    double * pSinTable;
    long tid;
};

void * compute(void * p) {
    Data * pDt = (Data *)p;
    const int start = sizen * pDt->tid/NUM_THREADS;
    const int end = sizen * (pDt->tid + 1)/NUM_THREADS;
    for(int n = start; n < end; ++n) {
        pDt->pSinTable[n] = std::sin(2 * M_PI * n / sizen);
    }
    pthread_exit(nullptr);
}

int main()
{
    double * sinTable = new double[sizen];
    pthread_t threads[NUM_THREADS];
    pthread_attr_t attr;
    pthread_attr_init(&attr);
    pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);

    clock_t start, finish;

    start = clock();
    int rc;
    Data dt[NUM_THREADS];
    for(int i = 0; i < NUM_THREADS; ++i) {
        dt[i].pSinTable = sinTable;
        dt[i].tid = i;
        rc = pthread_create(&threads[i], &attr, compute, &dt[i]);
    }//for
    pthread_attr_destroy(&attr);
    for(int i = 0; i < NUM_THREADS; ++i) {
        rc = pthread_join(threads[i], nullptr);
    }//for
    finish = clock();
    printf("from pthread: %lf\n", (double)(finish - start)/CLOCKS_PER_SEC);

    delete sinTable;
    sinTable = new double[sizen];

    start = clock();
#   pragma omp parallel for
    for(int n = 0; n < sizen; ++n)
        sinTable[n] = std::sin(2 * M_PI * n / sizen);
    finish = clock();
    printf("from omp: %lf\n", (double)(finish - start)/CLOCKS_PER_SEC);

    delete sinTable;
    sinTable = new double[sizen];

    start = clock();
    for(int n = 0; n < sizen; ++n)
        sinTable[n] = std::sin(2 * M_PI * n / sizen);
    finish = clock();
    printf("from serial: %lf\n", (double)(finish - start)/CLOCKS_PER_SEC);

    delete sinTable;

    pthread_exit(nullptr);
    return 0;
}

输出:

from pthread: 21.150000
from omp: 20.940000
from serial: 20.800000

我想知道这是否是我的代码问题所以我使用pthread做同样的事情。

但是,我完全错了,我想知道它是否是Ubuntu在OpenMP / pthread上的问题。

我有一个拥有AMD CPU和Ubuntu 12.04的朋友,并且在那里遇到了同样的问题,所以我可能有理由相信问题并不仅限于我。

如果有人和我有同样的问题,或者对这个问题有一些线索,请提前谢谢。


如果代码不够好,我运行基准测试并将结果粘贴到此处:

http://pastebin.com/RquLPREc

基准网址:http://www.cs.kent.edu/~farrell/mc08/lectures/progs/openmp/microBenchmarks/src/download.html


新信息:

我使用VS2012在Windows上运行代码(没有pthread版本)。

我使用了1/10的sizen,因为Windows不允许我分配那个结果为的大内存:

from omp: 1.004
from serial: 1.420
from FreeNickName: 735 (this one is the suggestion improvement by @FreeNickName)

这是否表明它可能是Ubuntu OS ??

的问题

使用可在操作系统中移植的omp_get_wtime函数解决问题。请参阅Hristo Iliev的答案。


FreeNickName对有争议主题的一些测试。

(抱歉,我需要在Ubuntu上进行测试,因为Windows是我的朋友之一&#39;。)

- 1--从delete更改为delete [] :(但没有memset)( - std = c ++ 11 -fopenmp)

from pthread: 13.491405
from omp: 13.023099
from serial: 20.665132
from FreeNickName: 12.022501

- 2--在新的后面紧跟memset:( - std = c ++ 11 -fopenmp)

from pthread: 13.996505
from omp: 13.192444
from serial: 19.882127
from FreeNickName: 12.541723

- 3--在新的后面紧跟memset:( - std = c ++ 11 -fopenmp -march = native -O2)

from pthread: 11.886978
from omp: 11.351801
from serial: 17.002865
from FreeNickName: 11.198779

- 4--在新版本之后立即使用memset,并在OMP版本之前放置FreeNickName的版本:( - std = c ++ 11 -fopenmp -march = native -O2)

from pthread: 11.831127
from FreeNickName: 11.571595
from omp: 11.932814
from serial: 16.976979

- 5--在新版本之后立即使用memset,并在OMP版本之前放置FreeNickName的版本,并将NUM_THREADS设置为5而不是2(我是双核心)。< / p>

from pthread: 9.451775
from FreeNickName: 9.385366
from omp: 11.854656
from serial: 16.960101

3 个答案:

答案 0 :(得分:6)

在您的情况下,OpenMP没有任何问题。你测量经过时间的方法有什么不对。

使用clock()来测量Linux(以及大多数其他类Unix操作系统)上的多线程应用程序的性能是一个错误,因为它不会返回挂钟(实际)时间,而是返回累计的CPU时间所有进程线程(以及某些Unix风格甚至所有子进程的累计CPU时间)。您的并行代码在Windows上显示出更好的性能,因为clock()返回的是实时而不是累计的CPU时间。

防止此类差异的最佳方法是使用便携式OpenMP计时器例程omp_get_wtime()

double start = omp_get_wtime();
#pragma omp parallel for
for(int n = 0; n < sizen; ++n)
    sinTable[n] = std::sin(2 * M_PI * n / sizen);
double finish = omp_get_wtime();
printf("from omp: %lf\n", finish - start);

对于非OpenMP应用程序,您应该将clock_gettime()CLOCK_REALTIME时钟一起使用:

struct timespec start, finish;
clock_gettime(CLOCK_REALTIME, &start);
#pragma omp parallel for
for(int n = 0; n < sizen; ++n)
    sinTable[n] = std::sin(2 * M_PI * n / sizen);
clock_gettime(CLOCK_REALTIME, &finish);
printf("from omp: %lf\n", (finish.tv_sec + 1.e-9 * finish.tv_nsec) -
                          (start.tv_sec + 1.e-9 * start.tv_nsec));

答案 1 :(得分:0)

在没有任何信息的情况下,Linux调度程序将倾向于在同一内核上的进程中调度线程,以便它们由相同的缓存和内存总线提供服务。它无法知道你的线程将访问不同的内存,所以不会受到伤害,而不是通过在不同的核心上帮助。

使用sched_setaffinity函数将每个线程设置为不同的核心掩码。

答案 2 :(得分:-2)

警告:这个答案是有争议的。下面描述的技巧取决于实现,可能导致性能下降。 不过,它也可能会增加它。我强烈建议您查看对此答案的评论。


这并没有真正回答这个问题,但如果改变代码并行化的方式,可能会提升性能。现在你这样做:

#pragma omp parallel for
for(int n = 0; n < sizen; ++n)
    sinTable[n] = std::sin(2 * M_PI * n / sizen);

在这种情况下,每个线程将计算一个项目。由于您有2个内核,OpenMP默认会创建两个线程。要计算线程必须具有的每个值:

  1. 初始化。
  2. 计算值。
  3. 第一步相当昂贵。你的两个线程都必须sizen/2次。 尝试执行以下操作:

        int workloadPerThread = sizen / NUM_THREADS;
        #pragma omp parallel for
        for (int thread = 0; thread < NUM_THREADS; ++thread)
        {
            int start = thread * workloadPerThread;
            int stop = start + workloadPerThread;
            if (thread == NUM_THREADS - 1)
                    stop += sizen % NUM_THREADS;
            for (int n = start; n < stop; ++n)
                sinTable[n] = std::sin(2 * M_PI * n / sizen);
        }
    

    这样你的线程只会初始化一次。