double compute_inner_contraction_0( double* X, double* T, int n, int f, int m, int e, int No, int Nv )
{
double contraction_value = 0.0;
int l_begin = 0;
int l_end = m;
for ( int l = l_begin; l < l_end; l++ )
{
int k_begin = 0;
int k_end = l;
for ( int k = k_begin; k < k_end; k++ )
{
contraction_value += (-1)*X[n * Nv * No * No * No * No + e * No * No * No * No + m * No * No * No + l * No * No + l * No + k]*T[k * Nv + f];
}
}
return contraction_value;
}
void compute_contraction_0( double* X, double* T, int No, int Nv, double* ExEx_block , int nthd )
{
omp_set_dynamic(0);
omp_set_num_threads(nthd);
#pragma omp parallel for
for ( int n = 0; n < No; n++ )
{
int f_begin = 0;
int f_end = Nv;
for ( int f = f_begin; f < f_end; f++ )
{
int m_begin = 0;
int m_end = n;
for ( int m = m_begin; m < m_end; m++ )
{
int e_begin = 0;
int e_end = f;
for ( int e = e_begin; e < e_end; e++ )
{
ExEx_block[n * Nv * No * Nv + f * No * Nv + m * Nv + e] += compute_inner_contraction_0( X, T, n, f, m, e, No, Nv);
}
}
}
}
}
嗨,大家好,很抱歉长期的功能。请不要担心内存泄漏的索引,因为它们已经过广泛测试。基本上它是在数组X和T中取数字并将它们相乘,然后将结果添加到另一个数组ExEx_block。此代码将在多线程上运行,但与在一个线程上运行的结果(这是正确的)相比,将给出非常错误的计算结果。能否请你帮忙?它构建为一个共享库(具有许多其他类似的函数),并从Python包装器加载。操作系统是CentOS 6,编译器是gcc-4.4.7
它是四核Xeon E5-4620(Sandy-EP)(总共32个核心)服务器,带有480 GB的DDR3。不,Nv只是计算中需要的整数。 nthd(线程数)通常只有10或20.这些数组的大小最多可达17 GB。非常感谢你的时间。
答案 0 :(得分:1)
我已经分析了您的代码,并且在更新ExEx_block
时您遇到了竞争条件。没有其他可能的方法可以获得错误的结果[因为其他一切只是读取数据]。
正好我们在同一页面上,我已经根据种族条件对 I 的含义进行了解释。
我还制作了程序的清理和简化版本,没有任何修复。但是,它为固定版本奠定了基础。
并且,第二个版本包含一些我认为您需要的omp
个示例指令(例如omp private
和omp atomic
)
请注意,我没有尝试编译这些示例,因此请将它们仅作为参考。
在您的程序中,如果两个不同的线程同时尝试更新ExEx_block[i]
,则会出现争用情况。为简化起见,我们使用int
标量代替(例如)target
。原则是一样的。
假设target
的值为5.我们有两个帖子Ta
和Tb
。 Ta
希望target += 3
和Tb
想做target += 7
。还假设每个线程分别具有私有变量ta
和tb
。
Ta
操作实际上是三个操作:
ta = target;
ta += 3
target = ta;
同样适用于Tb
:
tb = target;
tb += 7;
target = tb;
只要这些是不相交的,一切都很好,我们[最终]的target
值为15。
但是,如果任何操作散布在一起,我们可能 8或12而不是15的正确值:
ta = target;
tb = target;
ta += 3;
target = ta;
tb += 7;
target = tb;
您程序的简化版本。
请注意,我使用括号分组并使用int No4 = No * No * No * No
之类的内容来简化方程式并使其更具可读性。不是严格要求,但它帮助我发现了问题和解决方案。我试着小心,但我很容易弄乱了一个分组。
double
compute_inner_contraction_0(double *X,double *T,int n,int f,int m,int e,
int No,int Nv)
{
double contraction_value = 0.0;
int l_begin = 0;
int l_end = m;
int No2 = No * No;
int No3 = No2 * No;
int No4 = No3 * No;
for (int l = l_begin; l < l_end; l++) {
int k_begin = 0;
int k_end = l;
for (int k = k_begin; k < k_end; k++) {
contraction_value += (-1) *
X[(n * Nv * No4) + (e * No4) + (m * No3) + (l * No2) +
(l * No) + k] * T[(k * Nv) + f];
}
}
return contraction_value;
}
void
compute_contraction_0(double *X,double *T,int No,int Nv,double *ExEx_block,
int nthd)
{
omp_set_dynamic(0);
omp_set_num_threads(nthd);
int NoNv = Nv * No;
int NoNv2 = NoNv * Nv;
#pragma omp parallel for
for (int n = 0; n < No; n++) {
int f_begin = 0;
int f_end = Nv;
int nx = n * NoNv2;
for (int f = f_begin; f < f_end; f++) {
int m_begin = 0;
int m_end = n;
int fx = f * NoNv;
for (int m = m_begin; m < m_end; m++) {
int e_begin = 0;
int e_end = f;
int mx = m * Nv;
int ax = nx + fx + mx;
for (int e = e_begin; e < e_end; e++) {
ExEx_block[ax + e] +=
compute_inner_contraction_0(X,T,n,f,m,e,No,Nv);
}
}
}
}
}
您需要采取哪些措施来防止竞争状况。
为了提高效率,我将e
循环拆分为两个循环。一个执行繁重的计算,一个更新全局数组。这有助于最小化线程停滞,在繁重的计算阶段期间彼此等待。 &#34;锁定&#34;更新阶段是一个简单而快速的循环。
您需要使用omp&#39; private
指令的每线程本地数组,并且需要将omp atomic
添加到更新ExEx_block
的循环中。
警告:虽然我已经使用pthreads
完成了很多多线程编程,并知道如何修复竞争条件等,但我并不是很熟悉具体的等效omp
指令。
所以,根据我对omp文档的阅读,以下是我对你所需要的最好的猜测。所以,请...详细检查我在这里使用的指令。
double
compute_inner_contraction_0(double *X,double *T,int n,int f,int m,int e,
int No,int Nv)
{
double contraction_value = 0.0;
int l_begin = 0;
int l_end = m;
int No2 = No * No;
int No3 = No2 * No;
int No4 = No3 * No;
for (int l = l_begin; l < l_end; l++) {
int k_begin = 0;
int k_end = l;
for (int k = k_begin; k < k_end; k++) {
contraction_value += (-1) *
X[(n * Nv * No4) + (e * No4) + (m * No3) + (l * No2) +
(l * No) + k] * T[(k * Nv) + f];
}
}
return contraction_value;
}
void
compute_contraction_0(double *X,double *T,int No,int Nv,double *ExEx_block,
int nthd)
{
omp_set_dynamic(0);
omp_set_num_threads(nthd);
int NoNv = Nv * No;
int NoNv2 = NoNv * Nv;
// ExEx_local must be at least Nv:
// NOTE: placement here may be wrong
double ExEx_local[Nv];
#pragma omp parallel for private(ExEx_local)
for (int n = 0; n < No; n++) {
int f_begin = 0;
int f_end = Nv;
int nx = n * NoNv2;
for (int f = f_begin; f < f_end; f++) {
int m_begin = 0;
int m_end = n;
int fx = f * NoNv;
for (int m = m_begin; m < m_end; m++) {
int e_begin = 0;
int e_end = f;
int mx = m * Nv;
int ax = nx + fx + mx;
// separate computation from global update
for (int e = e_begin; e < e_end; e++) {
ExEx_local[e] =
compute_inner_contraction_0(X,T,n,f,m,e,No,Nv);
}
// now do global update
for (int e = e_begin; e < e_end; e++) {
#pragma omp atomic update
ExEx_block[ax + e] += ExEx_local[e];
}
}
}
}
}