在减少OpenMP中使用本征图

时间:2018-12-28 11:20:45

标签: c++ openmp eigen

我想结合使用特征矩阵和OpenMP简化。

下面是我如何做的一个小例子(它有效)。对象myclass具有三个属性(一个本征矩阵,两个对应于其维的整数)和一个成员函数do_something,该函数对我定义的总和使用omp归约,因为本征矩阵不是标准类型。

#include "Eigen/Core"

class myclass {
public:
    Eigen::MatrixXd m_mat;
    int m_n; // number of rows in m_mat
    int m_p; // number of cols in m_mat

    myclass(int n, int p); // constructor

    void do_something(); // omp reduction on `m_mat`
}

myclass::myclass(int n, int p) {
    m_n = n;
    m_p = p;
    m_mat = Eigen::MatrixXd::Zero(m_n,m_p); // init m_mat with null values
}

#pragma omp declare reduction (+: Eigen::MatrixXd: omp_out=omp_out+omp_in)\
    initializer(omp_priv=MatrixXd::Zero(omp_orig.rows(), omp_orig.cols()))

void myclass::do_something() {
    Eigen::MatrixXd tmp = Eigen::MatrixXd::Zero(m_n, m_p); // temporary matrix
#pragma omp parallel for reduction(+:tmp)
    for(int i=0; i<m_n;i++) {
        for(int l=0; l<m_n; l++) {
            for(int j=0; j<m_p; j++) {
                tmp(l,j) += 10;
            }
        }
    }
    m_mat = tmp;
}

问题:OpenMP不允许(或至少不是所有实现)对类成员使用归约,而仅对变量使用。因此,我在一个临时矩阵上进行了约简,并将此副本放在m_mat = tmp的末尾,我想避免这种情况(因为m_mat可能是一个大矩阵,并且我在我的文档中经常使用了此约简代码)。

错误的解决方法::我尝试使用特征映射,以使tmp对应于m_mat中存储的数据。因此,我用以下代码替换了前面代码中的omp简化声明和do_something成员函数定义:

#pragma omp declare reduction (+: Eigen::Map<Eigen::MatrixXd>: omp_out=omp_out+omp_in)\
    initializer(omp_priv=MatrixXd::Zero(omp_orig.rows(), omp_orig.cols()))

void myclass::do_something() {
    Eigen::Map<Eigen::MatrixXd> tmp = Eigen::Map<Eigen::MatrixXd>(m_mat.data(), m_n, m_p);
#pragma omp parallel for reduction(+:tmp)
    for(int i=0; i<m_n;i++) {
        for(int l=0; l<m_n; l++) {
            for(int j=0; j<m_p; j++) {
                tmp(l,j) += 10;
            }
        }
    }
}

但是,它不再起作用了,编译时出现以下错误:

  

错误:从'const ConstantReturnType {aka const   Eigen :: CwiseNullaryOp,   Eigen :: Matrix>}”转换为非标量类型   ‘Eigen :: Map,0,Eigen :: Stride <0,0>>”   要求的        初始值设定项(omp_priv = Eigen :: MatrixXd :: Zero(omp_orig.rows(),omp_orig.cols()))

我知道从Eigen::MatrixXdEigen::Map<Eigen::MatrixXd>的隐式转换在减少omp上不起作用,但是我不知道如何使它起作用。

预先感谢

编辑1:我忘了提到我在Ubuntu计算机上使用gcc v5.4(同时尝试16.04和18.04)

编辑2:我修改了示例,因为第一个示例没有减少。这个示例与我在代码中所做的不完全一样,只是一个最小的“哑”示例。

2 个答案:

答案 0 :(得分:4)

正如@ggael在回答中提到的,Eigen::Map不能用于此目的,因为它需要映射到现有存储。如果您确实做到了,那么所有线程都将使用相同的基础内存,这将创建竞争条件。

避免在初始线程中创建临时变量的最可能的解决方案是将成员变量绑定到引用,该引用在还原中应始终有效。看起来像这样:

void myclass::do_something() {
    Eigen::MatrixXd &loc_ref = m_mat; // local binding
#pragma omp parallel for reduction(+:loc_ref)
    for(int i=0; i<m_n;i++) {
        for(int l=0; l<m_n; l++) {
            for(int j=0; j<m_p; j++) {
                loc_ref(l,j) += 10;
            }
        }
    }
    // m_mat = tmp; no longer necessary, reducing into the original
}

也就是说,请注意,这仍然会在每个线程中创建零矩阵的本地副本,就像示例中显示的@ggael一样。以这种方式使用减少将非常昂贵。如果实际代码正在执行类似代码片段的操作,其中基于这样的嵌套循环添加值,则可以通过将工作划分为以下两种情况来避免这种减少:

  1. 每个线程接触矩阵的不同部分
  2. 原子用于更新单个值

解决方案1示例:

void myclass::do_something() {
    // loop transposed so threads split across l
#pragma omp parallel for
    for(int l=0; l<m_n; l++) {
        for(int i=0; i<m_n;i++) {
            for(int j=0; j<m_p; j++) {
                loc_ref(l,j) += 10;
            }
        }
    }
}

解决方案2示例:

void myclass::do_something() {
#pragma omp parallel for
    for(int i=0; i<m_n;i++) {
        for(int l=0; l<m_n; l++) {
            for(int j=0; j<m_p; j++) {
                auto &target = m_mat(l,j);
                // use the ref to get a variable binding through the operator()
                #pragma omp atomic
                target += 10;
            }
        }
    }
}

答案 1 :(得分:2)

问题是Eigen::Map只能在现有的内存缓冲区上创建。在您的示例中,底层的OpenMP实现将尝试执行以下操作:

Eigen::Map<MatrixXd> tmp_0 = MatrixXd::Zero(r,c);
Eigen::Map<MatrixXd> tmp_1 = MatrixXd::Zero(r,c);
...
/* parallel code, thread #i accumulate in tmp_i */
...
tmp = tmp_0 + tmp_1 + ...;

Map<MatrixXd> tmp_0 = MatrixXd::Zero(r,c)之类的东西当然是不可能的。 omp_priv必须是MatrixXd。我不知道是否可以自定义OpenMP创建的私有临时类型。如果不是这样,您可以通过创建std::vector<MatrixXd> tmps[omp_num_threads()];并自己进行最终还原来手工完成,或者做得更好:更好的方法是:不要为制作一个额外的副本而烦恼,与其他所有作品和副本相比,它几乎可以忽略不计由OpenMP本身完成。