我有一个关于使用OpenMP(使用C ++)的简单问题,我希望有人可以帮助我。我在下面列举了一个小例子来说明我的问题。
#include<iostream>
#include<vector>
#include<ctime>
#include<omp.h>
using namespace std;
int main(){
srand(time(NULL));//Seed random number generator
vector<int>v;//Create vector to hold random numbers in interval [0,9]
vector<int>d(10,0);//Vector to hold counts of each integer initialized to 0
for(int i=0;i<1e9;++i)
v.push_back(rand()%10);//Push back random numbers [0,9]
clock_t c=clock();
#pragma omp parallel for
for(int i=0;i<v.size();++i)
d[v[i]]+=1;//Count number stored at v[i]
cout<<"Seconds: "<<(clock()-c)/CLOCKS_PER_SEC<<endl;
for(vector<int>::iterator i=d.begin();i!=d.end();++i)
cout<<*i<<endl;
return 0;
}
上面的代码创建了一个向量v
,其中包含[0,9]
范围内的10亿个随机整数。然后,代码循环通过v
计算每个不同整数的实例数(即,在 v 中找到多少个,有多少两个等)
每次遇到特定整数时,都会通过递增向量d
的相应元素来计算。因此,d[0]
计算了多少个零,d[6]
计算了多少六个,依此类推。到目前为止有意义吗?
我的问题是当我尝试使计数循环并行时。如果没有#pragma OpenMP
语句,我的代码需要 20 秒,而使用pragma
则需要 60 秒。
显然,我误解了一些与OpenMP相关的概念(也许是如何共享/访问数据的?)。有人可以解释我的错误,或者指出一些具有相关关键词的有见识的文献,以帮助我搜索?
答案 0 :(得分:6)
你的代码exibits:
竞争条件的出现是因为您在多个线程中同时更新向量d
的相同元素。注释掉srand()
行并使用相同数量的线程(但具有多个线程)多次运行代码。比较不同运行的输出。
当两个线程写入彼此接近的内存位置时会发生错误共享,从而产生相同的缓存行。这导致高速缓存行在多串口系统中不断地从核心跳转到核心或CPU到CPU,以及过多的高速缓存一致性消息。每个高速缓存行有32个字节,向量的8个元素可以放在一个高速缓存行中。每个缓存行有64个字节,整个向量d
适合一个缓存行。这使得Core 2处理器上的代码变慢,而Nehalem和后Nehalem(例如Sandy Bridge)的代码稍慢(但不像Core 2那么慢)。真正的共享发生在两个或多个线程同时访问的元素上。您应该将增量放在OpenMP atomic
构造中(慢),使用OpenMP锁数组来保护对d
元素的访问(更快或更慢,具体取决于您的OpenMP运行时)或累积本地值,然后进行最终的同步减少(最快)。第一个实现如下:
#pragma omp parallel for
for(int i=0;i<v.size();++i)
#pragma omp atomic
d[v[i]]+=1;//Count number stored at v[i]
第二个是这样实现的:
omp_lock_t locks[10];
for (int i = 0; i < 10; i++)
omp_init_lock(&locks[i]);
#pragma omp parallel for
for(int i=0;i<v.size();++i)
{
int vv = v[i];
omp_set_lock(&locks[vv]);
d[vv]+=1;//Count number stored at v[i]
omp_unset_lock(&locks[vv]);
}
for (int i = 0; i < 10; i++)
omp_destroy_lock(&locks[i]);
(包括omp.h
以访问omp_*
个功能)
我让你想出第三个选项的实现。
您正在使用clock()
测量已用时间,但是它会测量CPU时间,而不是运行时。如果有一个线程以100%CPU使用率运行1秒钟,那么{{ 1}}表示CPU时间增加1秒。如果有8个线程以100%CPU使用率运行1秒,clock()
将指示CPU时间增加8秒(即8个线程乘以每个线程1个CPU秒)。请改用clock()
或omp_get_wtime()
(或其他一些高分辨率计时器API)。
答案 1 :(得分:1)
修改强> 一旦通过正确的同步解决了竞争条件,则适用以下段落,在此之前您的数据竞争条件不幸使速度比较静音:
您的程序正在减慢,因为在pragma部分中有10个可能的输出随机访问。如果没有锁定(您需要通过同步提供),OpenMP就无法访问任何这些元素,并且锁定将导致您的线程的开销高于并行计数所带来的开销。
使这种加速的解决方案是为每个OpenMP线程创建一个局部变量,该局部变量计算特定线程所看到的所有0-10值。然后在主计数向量中对它们求和。由于线程不需要锁定共享写向量,因此这将很容易并行化并且更快。我希望接近Nx加速,其中N是来自OpenMP的线程数,因为需要非常有限的锁定。此解决方案还避免了代码中当前的许多竞争条件。
有关线程本地OpenMP
的更多详细信息,请参阅http://software.intel.com/en-us/articles/use-thread-local-storage-to-reduce-synchronization/