OpenMP数组初始化影响



我在一个数组(工作部分)上与OpenMP并行工作。如果我之前并行初始化数组,那么我的工作部分需要18毫秒。如果我在没有OpenMP的情况下串行初始化数组,那么我的工作部分需要58毫秒。是什么导致性能变差?

系统:

  • Intel(R) Xeon(R) CPU E5-2697 v3(28核/56线程,2 Sockets)

示例代码:

unsigned long sum = 0;
long* array = (long*)malloc(sizeof(long) * 160000000);
// Initialisation
#pragma omp parallel for num_threads(56) schedule(static)
for(unsigned int i = 0; i < array_length; i++){
array[i]= i%10;
}

// Time start
// Work
#pragma omp parallel for num_threads(56) shared(array, 160000000) reduction(+: sum)
for (unsigned long i = 0; i < array_length; i++)
{
if (array[i] < 4)
{
sum += array[i];
}
}
// Time End

这里有两个方面在起作用:

NUMA分配在NUMA系统中,内存页对于CPU来说可以是本地的,也可以是远程的。默认情况下,Linux以先接触策略分配内存,这意味着对内存页的第一次写访问决定了该页在哪个节点上物理分配。

如果你的malloc足够大,从操作系统请求新的内存(而不是重用现有的堆内存),这个第一次触摸将在初始化时发生。因为您对OpenMP使用了静态调度,所以同一个线程将使用初始化它的内存。因此,除非线程被迁移到不同的CPU,否则内存将是本地的。

如果你不并行化初始化,内存最终会在主线程本地使用,这对于位于不同套接字上的线程来说会更糟。

请注意,Windows不使用第一次接触策略(AFAIK)。所以这个行为是不可移植的。

缓存同样也适用于缓存。初始化将把数组元素放入CPU的缓存中。如果同一个CPU在第二阶段访问内存,它将是缓存热的,可以使用。

首先,@Homer512的解释是完全正确的

现在我注意到您将这个问题标记为"c++",但是您使用的是malloc作为您的数组。这是坏的风格在c++:你应该使用std::vector为您的简单容器,std::array为足够小的。

然后你有一个大问题,因为std::vector使用"值初始化":整个数组自动填充零,没有办法让这个与OpenMP并行完成。

这里有一个大技巧:

template<typename T> 
struct uninitialized {
uninitialized() {};
T val;
constexpr operator T() const {return val;};
double operator=( const T&& v ) { val = v; return val; };
};
vector<uninitialized<double>> x(N),y(N);
#pragma omp parallel for 
for (int i=0; i<N; i++)
y[i] = x[i] = 0.; 
x[0] = 0; x[N-1] = 1.;

最新更新