OpenMP中的本地指针

local pointers in OpenMP

本文关键字:指针 OpenMP      更新时间:2023-10-16

局部变量应该自动成为每个线程的私有变量。一个本地指针指向平行区域之外的某个地址,比如,怎么样

A * a = new A[10];
int i, j;
for (i = 0; i < 10; i++){
A * local_i = &a[i];
// do sth ...
#pragma omp parallel for
for (j = 0; j < 10; j++){
A * local_j = &a[j];
local_j->x = 1.0f;
// ...
}
}
delete[]a;

我应该将local_a设为私有,将a设为非私有吗?实际上,我是OpenMP和C的新手。

重要的是要知道OpenMP对待静态和动态数组的方式不同。在您给出的示例中,静态数组更合适。让我们看看在静态和动态数组上使用共享、私有和firstprivate时会发生什么。我将为每种情况打印线程号、a的地址、a的值和数组的值。

静态阵列:

int a[10];
for(int i=0; i<10; i++) a[i]=i;
#pragma omp parallel
{
#pragma omp critical
{
printf("ithread %d %p %p :", omp_get_thread_num(), &a, a); for(int i=0; i<10; i++) printf("%d ", a[i]); printf("n");             
}
}
//ithread 1 0x7fff3f43f9b0 0x7fff3f43f9b0 :0 1 2 3 4 5 6 7 8 9 
//ithread 3 0x7fff3f43f9b0 0x7fff3f43f9b0 :0 1 2 3 4 5 6 7 8 9 

请注意,每个线程都有相同的a地址。现在让我们尝试将a作为私有传递。

#pragma omp parallel private(a)
//ithread 0 0x7fffc7897d60 0x7fffc7897d60 :4 0 -1393351936 2041147031 4 0 0 0 4196216 0 
//ithread 1 0x7fa65f275df0 0x7fa65f275df0 :0 0 0 0 0 0 1612169760 32678 1596418496 32678 

现在,每个线程都有其唯一的私有a,并且每个私有版本都指向不同的内存地址。但是,数组的值没有被复制。现在让我们试试firstprivate(a)

#pragma omp parallel firstprivate(a)
//ithread 0 0x7ffffb5ba860 0x7ffffb5ba860 :0 1 2 3 4 5 6 7 8 9 
//ithread 3 0x7f50a8272df0 0x7f50a8272df0 :0 1 2 3 4 5 6 7 8 9 

现在唯一的区别是a的值被复制了。

动态阵列:

int *a = new int[10];
for(int i=0; i<10; i++) a[i]=i;

让我们首先来看看将a作为共享传递

#pragma omp parallel
//ithread 2 0x7fff86a02cc8 0x9ff010 :0 1 2 3 4 5 6 7 8 9 
//ithread 0 0x7fff86a02cc8 0x9ff010 :0 1 2 3 4 5 6 7 8 9

每个线程都有相同的a,就像一个静态数组一样。当我们使用private时,就会发生差异。

#pragma omp parallel private(a)
//segmentation fault

每个线程都有自己的私有a,就像静态数组一样,但每个版本指向的内存地址是未分配的随机内存。当我们试图读取它时,我们会遇到分段错误。我们可以使用firstprivate(a)解决此问题

#pragma omp parallel firstprivate(a)
//ithread 0 0x7fff2baa2b48 0x8bd010 :0 1 2 3 4 5 6 7 8 9 
//ithread 1 0x7f3031fc5e28 0x8bd010 :0 1 2 3 4 5 6 7 8 9

现在我们看到,每个线程都有自己的私有a,然而,与静态数组不同的是,每个线程仍然指向相同的内存地址。因此指针是私有的,但它们指向的地址是相同的。这实际上意味着内存仍然是共享的。

如何分配动态数组的私有版本

为了获得每个线程的动态数组的私有版本,我不建议在并行区域之外分配它们。原因是,如果你不小心,很容易导致虚假分享。请参阅这个关于错误共享的问题/答案,该错误共享是由分配并行区域之外的内存OpenMP实现的减少引起的。你可以使用双指针,但这不一定能解决错误的共享问题,也不会解决多套接字系统上的另一个问题。在这些系统中,重要的是套接字不能共享同一个页面(或者你会得到另一种错误的共享)。如果让每个线程分配内存,就不必担心这一点。

通常,我会为每个线程分配一个数组的私有版本,然后将它们合并到一个关键部分。然而,有些情况只需要分配一次,但在不使用关键部分的情况下,与OpenMP并行执行正确的填充直方图(数组缩减)是很复杂的。

这取决于您想对数组做什么。如果每个线程访问相同的数组,则不需要将指针设置为threadprivate。

在您的情况下,是否将a设置为私有并不重要,因为指针只存储指向内存的地址。如果所有线程都使用相同的变量访问相同的地址,或者每个线程都有相同地址的自己的副本,这没有什么区别。

如果每个线程都应该有自己的数组副本,那么您需要分别为每个线程分配和删除内存。如果您的编译器支持为线程专用对象的已创建C++对象调用正确的构造函数,那么您可以在您的情况下使用std::vector。

std::vector<A> a;
#pragma omp parallel for firstprivate(a)
for (j = 0; j < 10; j++)
{
A * local_j = &a[j];
local_j->x = 1.0f;
// ...
}

否则,您需要为每个线程分配一个单独的内存块。

const int num_threads = omp_get_num_threads(); 
A** a;
a = new A*[num_threads];
for (int i=0; i<num_threads; i++)
a[i] = new A[10];
#pragma omp parallel for firstprivate(a)
for (j = 0; j < 10; j++)
{
const int thread_id = omp_get_thread_num();
A * local_j = &a[thread_id][j];
local_j->x = 1.0f;
// ...
}
for (int i=0; i<num_threads; i++)
delete [] a[i];
delete [] a;