openmp for循环错误导致多个线程

时间:2016-08-18 18:36:14

标签: c openmp

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。非常感谢你的时间。

1 个答案:

答案 0 :(得分:1)

我已经分析了您的代码,并且在更新ExEx_block时您遇到了竞争条件。没有其他可能的方法可以获得错误的结果[因为其他一切只是读取数据]。

正好我们在同一页面上,我已经根据种族条件对 I 的含义进行了解释。

我还制作了程序的清理和简化版本,没有任何修复。但是,它为固定版本奠定了基础。

并且,第二个版本包含一些我认为您需要的omp个示例指令(例如omp privateomp atomic

请注意,我没有尝试编译这些示例,因此请将它们仅作为参考。

在您的程序中,如果两个不同的线程同时尝试更新ExEx_block[i],则会出现争用情况。为简化起见,我们使用int标量代替(例如)target。原则是一样的。

假设target的值为5.我们有两个帖子TaTbTa希望target += 3Tb想做target += 7。还假设每个线程分别具有私有变量tatb

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];
                }
            }
        }
    }
}