简体   繁体   中英

C++ Efficient arithmetic operator overloading for class containing Eigen matrices

I want to construct a class that contains several Eigen Matrices or Vectors. I want the objects to behave as mathematical vectors: therefore I want to overload +, - and *(by a scalar). I want to do very many linear combinations of these objects, so I would like to implement it in a way that it takes full advantage of Eigen and creates as few copies and temporaries as possible.

I usually follow the recommendations in What are the basic rules and idioms for operator overloading? but they require to pass some of the object by value rather than reference. Eigen "dislikes" when matrices are passed by value.

Below is my attempt (I overload only + for shortness) where I avoid passing by value in the overloading of + and instead I use the named return value optimization (NRVO):

template <int N> class myClass {
public:
   Eigen::Matrix<double, N, N> mat = Eigen::Matrix<double, N, N>::Zero();
   Eigen::Matrix<double, N, 1> vec = Eigen::Matrix<double, N, 1>::Zero();
public:
    myClass(){}
    myClass(Eigen::Matrix<double, N, N> & t_mat,Eigen::Matrix<double, N, 1> &t_vec) : mat (t_mat), vec(t_vec){ }
    myClass(const myClass & other): mat(other.mat), vec(other.vec) {};
    myClass& operator+=(const myClass& rhs){
        mat += rhs.mat;      vec += rhs.vec;
        return *this;
    }

public:
        enum { NeedsToAlign = (sizeof(mat)%16)==0 || (sizeof(vec)%16)==0 };
        EIGEN_MAKE_ALIGNED_OPERATOR_NEW_IF(NeedsToAlign)
};

template <int N> myClass<N>& operator+( const myClass<N>& lhs, const myClass<N>& rhs )
{
   myClass<N> nrv( lhs );
   nrv += rhs;
   return nrv;
}

Is this the best solution? Fastest?

As a final short question: it would be nice to use Boost/operators ( https://www.boost.org/doc/libs/1_54_0/libs/utility/operators.htm ) to easily complete the set of operator overloading. Can I use that, or would that cause problems? I guess it will implement binary operations passing the first argument by value.

I tried to solve the problem by myself. I tried several versions and compared them with directly summing Eigen vectors.

The first strategy follows What are the basic rules and idioms for operator overloading? :

using realValueType = double;
const int NDim = 1000000;
using InternalVecType = Eigen::Matrix<realValueType, Eigen::Dynamic, 1>;

class MyVec {
public:
    InternalVecType vec;
    // ... Other properties
    
public:
    // Constructors
    MyVec(): vec(InternalVecType::Random(NDim,1)) {}    // Initialises as random, just for testing
    MyVec(const InternalVecType& other): vec(other) {};
    MyVec(const MyVec& other): vec(other.vec) {};       // Copy constructor
    MyVec(MyVec&& other){vec.swap(other.vec);}          // Move constructor
    
    // Arithmetic
    MyVec& operator=(MyVec other){vec.swap(other.vec);return *this;} // Copy-and-Swap
    MyVec& operator+=(const MyVec& other) {vec+=other.vec; return *this;}
    friend MyVec operator+(MyVec lhs, const MyVec& rhs) { lhs += rhs; return lhs;}
};

The second version uses NRVO

class MyVecNRVO {
public:
    InternalVecType vec;
    // ... Other properties
    
public:
    // Constructors
    MyVecNRVO(): vec(InternalVecType::Random(NDim,1)) {}    // Initialises as random, just for testing
    MyVecNRVO(const InternalVecType& other): vec(other) {};
    MyVecNRVO(const MyVec& other): vec(other.vec) {};       // Copy constructor
    MyVecNRVO(MyVec&& other){vec.swap(other.vec);}          // Move constructor
    
    // Arithmetic
    MyVecNRVO& operator=(MyVecNRVO other){vec.swap(other.vec);return *this;} // Copy-and-Swap
    MyVecNRVO& operator+=(const MyVecNRVO& other) {vec+=other.vec; return *this;}
    friend MyVecNRVO operator+(const MyVecNRVO& lhs, const MyVecNRVO& rhs) {
        MyVecNRVO nrv(lhs);  nrv += rhs; return nrv;}
};

My third version uses (probably clumsily) expression templates

struct Add { static InternalVecType apply(InternalVecType lhs, InternalVecType rhs) {return lhs+rhs;} };

template<class E1, class E2, class Op> struct ComplexExpr {
    E1 l_;
    E2 r_;
    ComplexExpr(E1 l, E2 r) : l_(l), r_(r) {}
    InternalVecType getvec(){return Op::apply(l_.getvec(), r_.getvec());}
};

template<class E1, class E2> ComplexExpr<E1, E2, Add> operator+ (E1 e1, E2 e2) {
    return ComplexExpr<E1, E2, Add>( e1, e2 );
}

class MyVecT {
public:
    InternalVecType vec;
    // ... Other properties

public:
    // Constructors
    MyVecT(): vec(InternalVecType::Random(NDim,1)) {}    // Initialises as random, just for testing
    MyVecT(const InternalVecType& other): vec(other) {};
//    template<> MyVecT(

    InternalVecType getvec(){return vec;};
};

To test it I created 15 vectors of types InternalVecType (meaning the Eigen type not wrapped in my class), MyVec , MyVecNRVO , and MyVecT and summed them in a single line.

What I have noticed is that the first version is not only the fastest, but also as fast as directly adding the Eigen vectors in a single line, NRVO is around 5 times slower, while my attempt at expression templates 100 times slower (ouch: but in my defence. it is my very first attempt ever :D).

It seems that the first option is not only the best, but also the optimal. Maybe a better implementation of expression templates could achieve a better speed, but since the first option is as fast as the raw summation, it does not seem worth to go in that direction. Any comment is welcome.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM