2014-05-19 52 views
2

我正在写一个程序,其中一堆不同的类,所有存储在向量中,使用公共数据结构在私有成员上执行并行操作。我想为使用OpenMP的多个处理器并行化它,但是我有两个有关代码中两个操作的问题,下面的示例在下面的注释中指出了这两个问题,它们显示了程序逻辑的简化形式。是只读的全局变量和写入私人类成员在openmp中的“虚假共享”

#include <omp.h> 
#include <iostream> 
#include <sys/timeb.h> 
#include <vector> 

class A { 
    private : 
    long _i; 
    public : 
    void add_i(long &i) { _i += i; } 
    long get_i() const { return _i; } 
}; 

int main() 
{ 
    timeb then; 
    ftime(&then); 
    unsigned int BIG = 1000000; 
    int N = 4; 
    std::vector<long> foo(BIG, 1); 
    std::vector<A *> bar; 
    for (unsigned int i = 0; i < N; i++) 
    { 
    bar.push_back(new A()); 
    } 
    #pragma omp parallel num_threads(4) 
    { 
    for(long i = 0; i < BIG; i++) 
    { 
     int thread_n = omp_get_thread_num(); 
     // read a global variable 
     long *to_add = &foo[i]; 
     // write to a private variable 
     bar[thread_n]->add_i(*to_add); 
    } 
    } 
    timeb now; 
    ftime(&now); 
    for (int i = 0; i < N; i++) 
    { 
    std::cout << bar[i]->get_i() << std::endl; 
    } 
    std::cout << now.millitm - then.millitm << std::endl; 
} 

第一条评论涉及从全局foo读取。这是“虚假分享”(或数据晃荡)?我读的大部分资源都是关于写入操作方面的虚假共享,但我不知道读取操作是否也适用。

第二个注释解决了对bar中的类的写入操作。同样的问题:这是错误的分享?他们正在写入同一个全局数据结构中的元素(这是我读过的,晃荡),但只是在元素内部对私有数据进行操作。

当我与一个for循环替换OpenMP的宏,该程序是由约25%的速度,所以我猜我做错了什么......

+0

你的例子很奇怪。你说过一堆不同的课程,但我认为你是指同一班级的一堆不同的对象?而你的意思是四个。这不是很多。但它也被设置为似乎任意的线程数。你为什么不向我们展示你的非并行代码,然后我们可以告诉你如何最好地并行化它。 –

回答

0

你最大共享问题是bar[thread_n]foo[i]的阅读不成问题。

编辑:由于bar[thread_n]是持有指针,指针是什么得到更新,很少或没有共享。您仍然可以从每个CPU内核中加载“一次一块”,而不是从每个缓存行读取每个CPU内核的一个或两个项目。所以下面的代码仍然可以受益。一如既往,当它是一个性能问题时,基准测试(启用优化)很多,因为不同的系统会有不同的表现(取决于编译器,CPU架构,内存子系统等)

这会更好地“ “在每个线程中一次一些项目。可能是这样的:

const int STEP=16; 

for(long i = 0; i < BIG; i+=STEP) 
{ 
    int thread_n = omp_get_thread_num(); 
    int stop = std::min(BIG-i, STEP); // Don't go over edge. 
    for(j = 0; j < stop; j++) 
    { 
    // read a global variable  
    long *to_add = &foo[i+j]; 
    // write to a private variable 
    bar[thread_n*STEP + j]->add_i(*to_add); 
    } 
} 

您可能需要调整“STEP”以使其达到合适的水平。

+0

他的酒吧对象是堆上的“新”。谁知道他们最终在哪里。在我的笔记本电脑上,分配粒度为32字节,高速缓存行为64.两个对象可能会在同一高速缓存行中结束。 – Anycorn

+0

但是矢量'bar'是一段连续的记忆。 –

+0

这是指针矢量,'矢量'而不是'矢量' – Anycorn

1

现代内存分配器是线程感知的。也

const int N = 4; 
std::vector<long> foo(BIG, 1); 
std::vector<A *> bar(N); 
#pragma omp parallel num_threads(N) 
{ 
    int thread_n = omp_get_thread_num(); 
    bar[thread_n] = new A(); 
    for(long i = 0; i < BIG; i++) 
    { 
    // read a global variable 
    long *to_add = &foo[i]; 
    // write to a private variable 
    bar[thread_n]->add_i(*to_add); 
    } 
} 

注意,在这种情况下omp_get_thread_num()被称为:为防止假共享,当涉及到修改每个实例的class A指向的bar的元素,你应该将并行区域内的内存分配,例如,只有一次,而不是您的代码中的BIG次。调用一个函数的开销相对较低,但是当你多次执行时,它会加起来。

+0

我不认为''我是这种情况,并行区域内声明的变量是自动私有的。 – Anycorn

+0

已修复。我有点错过了在循环结构中声明'i'的事实。 –