C++ 在OpenMP缩减中使用特征映射

C++ 在OpenMP缩减中使用特征映射,c++,openmp,eigen,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

我想将特征矩阵与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;
}
但是,它不再工作,我在编译时遇到以下错误:

错误:从“const ConstantReturnType{aka const”转换 本征::CwiseNullaryOp, 特征::矩阵>}'到非标量类型 '本征::映射,0,本征::跨步>' 请求 初始值设定项(omp_priv=Eigen::MatrixXd::Zero(omp_orig.rows(),omp_orig.cols())

我发现从
Eigen::MatrixXd
Eigen::Map
的隐式转换在omp缩减中不起作用,但我不知道如何使其起作用

提前谢谢

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


编辑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 + ...;
Eigen::Map tmp_0=MatrixXd::Zero(r,c);
本征::映射tmp_1=MatrixXd::零(r,c);
...
/*并行代码,线程i在tmp#u i中累积*/
...
tmp=tmp_0+tmp_1+。。。;

当然,像Map tmp_0=MatrixXd::Zero(r,c)这样的东西是不可能的
omp_priv
必须是
MatrixXd
。我不知道是否可以自定义OpenMP创建的私有临时表的类型。如果没有,您可以手动创建
std::vector tmps[omp_num_threads()]
并自己完成最终的缩减,或者更好:不必再费心制作一个额外的拷贝,与OpenMP本身完成的所有其他工作和拷贝相比,它在很大程度上可以忽略不计

正如@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
}
void myclass::do_something(){
Eigen::MatrixXd&loc_ref=m_mat;//本地绑定
#pragma omp并行还原(+:loc_ref)

对于(int i=0;我不太熟悉如何实现Eigen的细节,但我假设它们支持移动语义,在这种情况下,这似乎是最简单的解决方案?(事实上,你确定不会发生这种情况吗?)您的意思是,当我执行
m_mat=tmp
时,它会将与
m_mat
相关的指针更改为指向
tmp
,而不是将
tmp
硬拷贝到
m_mat
?您的示例没有实现缩减。每个线程将处理自己的
tmp
行集,而不进行并发访问。是的,我是的,我正在修改它。我没有在我的代码中完全做到这一点,我没有将其简化为一个最小的示例。谢谢谢谢你,我现在理解得更好了。谢谢你的补充评论。我的算法确实与这个小示例不同。我想你首先提出的方法在我的情况下会起作用。我知道我无法避免内线程馅饼,但那不是问题。再次谢谢
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
}
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;
            }
        }
    }
}
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;
            }
        }
    }
}