【问题标题】:Parallelization of Jacobi algorithm using eigen c++ using openmp使用 openmp 使用 eigen c++ 并行化 Jacobi 算法
【发布时间】:2016-10-05 17:31:45
【问题描述】:

我已经根据《数值食谱》一书中描述的例程实现了 Jacobi 算法,但由于我计划处理非常大的矩阵,因此我尝试使用 openmp 对其进行并行化。

void ROTATE(MatrixXd &a, int i, int j, int k, int l, double s, double tau)
{
double g,h;
g=a(i,j);
h=a(k,l);
a(i,j)=g-s*(h+g*tau);
a(k,l)=h+s*(g-h*tau);

}

void jacobi(int n, MatrixXd &a, MatrixXd &v, VectorXd &d )
{
int j,iq,ip,i;
double tresh,theta,tau,t,sm,s,h,g,c;

VectorXd b(n);
VectorXd z(n);

v.setIdentity();    
z.setZero();

#pragma omp parallel for 
for (ip=0;ip<n;ip++)
{   
    d(ip)=a(ip,ip);
    b(ip)=d(ip);
}

for (i=0;i<50;i++) 
{
    sm=0.0;
    for (ip=0;ip<n-1;ip++) 
    {
        #pragma omp parallel for reduction (+:sm)
        for (iq=ip+1;iq<n;iq++)
            sm += fabs(a(ip,iq));
    }
    if (sm == 0.0) {
        break;
    }

    if (i < 3)
    tresh=0.2*sm/(n*n); 
    else
    tresh=0.0;  

    #pragma omp parallel for private (ip,g,h,t,theta,c,s,tau)
    for (ip=0;ip<n-1;ip++)
    {
    //#pragma omp parallel for private (g,h,t,theta,c,s,tau)
        for (iq=ip+1;iq<n;iq++)
        {
            g=100.0*fabs(a(ip,iq));
            if (i > 3 && (fabs(d(ip))+g) == fabs(d[ip]) && (fabs(d[iq])+g) == fabs(d[iq]))
            a(ip,iq)=0.0;
            else if (fabs(a(ip,iq)) > tresh)
            {
                h=d(iq)-d(ip);
                if ((fabs(h)+g) == fabs(h))
                {
                    t=(a(ip,iq))/h;
                }   
                else 
                {
                    theta=0.5*h/(a(ip,iq));
                    t=1.0/(fabs(theta)+sqrt(1.0+theta*theta));
                    if (theta < 0.0)
                    {
                        t = -t;
                    }
                    c=1.0/sqrt(1+t*t);
                    s=t*c;
                    tau=s/(1.0+c);
                    h=t*a(ip,iq);

                   #pragma omp critical
                    {
                    z(ip)=z(ip)-h;
                    z(iq)=z(iq)+h;
                    d(ip)=d(ip)-h;
                    d(iq)=d(iq)+h;
                    a(ip,iq)=0.0;


                    for (j=0;j<ip;j++)
                        ROTATE(a,j,ip,j,iq,s,tau);
                    for (j=ip+1;j<iq;j++)
                        ROTATE(a,ip,j,j,iq,s,tau);
                    for (j=iq+1;j<n;j++)
                        ROTATE(a,ip,j,iq,j,s,tau);
                    for (j=0;j<n;j++)
                        ROTATE(v,j,ip,j,iq,s,tau);
                    }

                }
            } 
        }
    }


}

}

我想并行化执行大部分计算的循环以及代码中插入的两个 cmets:

 //#pragma omp parallel for private (ip,g,h,t,theta,c,s,tau)
 //#pragma omp parallel for private (g,h,t,theta,c,s,tau)

是我的尝试。不幸的是,它们最终都产生了不正确的结果。我怀疑问题可能出在这个块:

z(ip)=z(ip)-h;
z(iq)=z(iq)+h;
d(ip)=d(ip)-h;
d(iq)=d(iq)+h;

因为通常这种累积需要减少,但由于每个线程访问数组的不同部分,我不确定这一点。

我不确定我是否以正确的方式进行并行化,因为我最近才开始使用 openmp,因此也欢迎任何建议或建议。

旁注:我知道有更快的特征值和特征向量确定算法,包括 Eigen 中的 SelfAdjointEigenSolver,但这些算法并没有给我在特征向量中所需的精度,而这个算法是。

提前致谢。

编辑:我认为正确的答案是 The Quantum Physicist 提供的答案,因为我所做的并没有减少最大为 4096x4096 的系统的计算时间。无论如何,我更正了代码以使其正常工作,也许对于足够大的系统,它可能会有一些用处。我建议使用计时器来测试

#pragma omp for

实际上减少了计算时间。

【问题讨论】:

  • 我没有确认并运行此代码...但我猜您的 ROTATE 函数在线程之间存在争用。为了证明这一点,您可以尝试输入#pragma omp critical { ... }。此外,除非您需要符合 C89 标准(您使用的是 C++,所以..)...我个人发现将参数范围缩小到最窄范围要容易得多,它使私有/共享在语义上更加明显。
  • 关于你的旁注,很高兴弄清楚为什么SelfAdjointEigenSolver 没有给你预期的准确性。为此,您能否向我发布/发送一个独立的示例或您的问题矩阵之一,以便我们可以进行调查? (你会在几乎所有 Eigen 的头文件中找到我的电子邮件)。谢谢。
  • 元问题:为什么要自己写?为什么不使用现有的优化数学库中的一个。英特尔 MKL 免费提供 software.intel.com/en-us/articles/free-mkl 并包含 Jacobi 函数。 software.intel.com/en-us/node/522101 我不确定它们是否并行化,但 MKL 的其他部分肯定是并行化的。 (“最好的代码是我不必编写的代码”)。 (FWIW 我为英特尔工作,但不在 MKL)。
  • 感谢您的建议,我会检查一下。我想获得特征值和特征向量不能解决线性系统,但也许英特尔库中也有这样做的东西。

标签: c++ algorithm parallel-processing openmp eigen


【解决方案1】:

我会尽力提供帮助,但我不确定这是否是您问题的答案。

您的代码存在大量问题。我对你的友好建议是:如果你不了解你所做的事情的含义,就不要做平行的事情。

出于某种原因,您似乎认为将所有内容并行处理#pragma for 会使其更快。这是非常错误的。因为产生线程是一件昂贵的事情,并且(相对)花费大量的内存和时间。因此,如果您为每个循环重做 #pragma for,您将为每个循环重新生成线程,这将显着降低程序的速度...除非:您的矩阵非常庞大并且计算时间 >> 比成本产生它们。

当我想以元素方式乘以巨大的矩阵时,我遇到了类似的问题(然后我需要求和以获得量子力学中的一些期望值)。为了使用 OpenMP,我必须将矩阵展平为线性数组,然后将每个数组块分配给一个线程,然后运行一个 for 循环,其中每个循环迭代肯定使用独立于其他元素的元素,我使它们都独立发展。这是相当快的。为什么?因为我从来不用两次重生线程。

为什么你得到错误的结果?我相信原因是因为您不尊重共享内存规则。您有 一些 变量同时被多个线程修改。它藏在某个地方,你必须找到它!比如z这个函数有什么作用?它是否通过引用来获取东西?我在这里看到的:

z(ip)=z(ip)-h;
z(iq)=z(iq)+h;
d(ip)=d(ip)-h;
d(iq)=d(iq)+h;

看起来非常多线程不安全,我不明白你在做什么。您是否返回必须修改的参考?这是线程不安全的秘诀。你为什么不创建干净的数组并处理它们而不是这个?

如何调试:从一个小例子开始(可能是 2x2 矩阵),只使用 2 个线程,并尝试了解发生了什么。使用调试器并定义断点,并检查线程之间共享哪些信息。

还可以考虑使用互斥锁来检查哪些数据在共享时会被破坏。 Here 是怎么做的。

我的建议:除非您打算只生成一次线程,否则不要使用 OpenMP。实际上,我相信 OpenMP 很快就会因为 C++11 而消亡。当 C++ 没有任何本机多线程实现时,OpenMP 很漂亮。所以学习如何使用std::thread,并使用它,如果你需要在线程中运行很多东西,那么学习如何使用std::thread创建一个线程池。 This 是一本学习多线程的好书。

【讨论】:

  • OpenMP 和 C++ 线程无法实现相同的目标。 OpenMP、Cilk++、OpenACC 等并行扩展旨在重写针对特定平台(协处理器、GPU、cpu/numa 架构)的 C/C++ 代码。 std::thread 专为通用线程而设计,但需要您了解实际优化的具体目标。
  • @cjhanks 这有待商榷。恕我不能赞同。我最后说的只是我的看法。
  • 矩阵可以是 4096*4096 不大于此。我知道生成不同的线程可能效率低下,但我计划使用计时器来测试速度。至于你的问题,z 是 Eigen 中的一个数组,我正在做的是访问一个系数。
  • @jcarvalho 这个尺寸即将变大,但实际上并没有那么大。查看数学:每个生成的线程平均占用大约 1 MB 的内存(从我在答案中列出的书中了解到)。该矩阵将占用 4096^2 双倍,即大约 120 MB 的内存。因此,如果你生成 20 个线程,你的线程的成本大约是你的数据的 17%,因此,生成线程的成本是不可忽略的。所以我建议你以一种避免多次产生线程的方式来运行你的程序。
  • 是的,你是对的,我得到了 z(ip)=z(ip)-h; 的代码工作。和关键块中的 ROTATE() 部分,但并行化都没有提高代码的性能,有些甚至大大降低了它。这样做的正确方法是如你所说减少我产生线程的次数,但在这种情况下,我不认为这是值得的工作,谢谢。我编辑了问题以添加解决问题的解决方案,因为对于足够大的系统,这可能会有所帮助。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2021-09-20
  • 1970-01-01
  • 1970-01-01
  • 2018-06-17
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多