我已经阅读了关于这个主题的其他一些问题。然而,他们并没有解决我的问题。
我写的代码如下,我得到的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: %lfn", (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: %lfn", (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: %lfn", (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/phread上的问题。
我有一个朋友,他有AMD CPU和Ubuntu 12.04,也遇到了同样的问题,所以我可能有理由相信这个问题不仅仅局限于我。
如果有人和我有同样的问题,或者对这个问题有一些线索,请提前感谢。
如果代码不够好,我运行了一个基准测试,并将结果粘贴到这里:
http://pastebin.com/RquLPREc
基准url:http://www.cs.kent.edu/~farrell/mc08/archips/progs/openmp/microBenchmarks/src/download.html
新信息:
我用VS2012在windows(没有pthread版本)上运行了代码。
我使用了sizen的1/10,因为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是我朋友的。)
--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——在new之后立即使用memset,并将FreeNickName的版本放在OMP之前,用于版本:(-std=c++11-fopenmp-march=native-O2)
from pthread: 11.831127
from FreeNickName: 11.571595
from omp: 11.932814
from serial: 16.976979
--5——在new之后立即使用memset,并将FreeNickName的版本放在版本的OMP之前,并将NUM_THREADS
设置为5而不是2(我是双核)。
from pthread: 9.451775
from FreeNickName: 9.385366
from omp: 11.854656
from serial: 16.960101
在您的案例中,OpenMP没有任何问题。错误的是你测量逝去时间的方式。
在Linux(以及大多数其他类似Unix的操作系统)上使用clock()
来测量多线程应用程序的性能是一个错误,因为它不返回墙上的时钟(实时)时间,而是返回所有进程线程的累积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: %lfn", 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: %lfn", (finish.tv_sec + 1.e-9 * finish.tv_nsec) -
(start.tv_sec + 1.e-9 * start.tv_nsec));
在没有任何信息的情况下,Linux调度器倾向于在同一核心上调度进程中的线程,以便它们由相同的缓存和内存总线提供服务。它无法知道你的线程将访问不同的内存,所以在不同的内核上不会受到伤害,反而会有所帮助。
使用sched_setaffinity函数将每个线程设置为不同的核心掩码。
警告:这个答案有争议。下面描述的技巧取决于实现,可能会导致性能下降尽管如此,它也可能会增加它。我强烈建议大家看看对这个答案的评论。
这并不能真正回答问题,但如果你改变代码并行化的方式,你可能会得到性能提升。现在你这样做:
#pragma omp parallel for
for(int n = 0; n < sizen; ++n)
sinTable[n] = std::sin(2 * M_PI * n / sizen);
在这种情况下,每个线程将计算一个项目。由于您有两个核心,OpenMP默认情况下将创建两个线程。要计算每个值,线程必须:
- 初始化
- 计算值
第一步相当昂贵。两个线程都必须执行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);
}
这样,线程将只初始化一次。