How should I use Eigen's data structures?

194 views Asked by At

I would like to know how data structures are managed in Eigen.

To be more exact, I would like to know if I should pass them as pointers, or perhaps they actually use some smart pointers.

For example, would the following function make sense:

SparseVector<double> retVector()
{
     SparseVector<Double> vec(3);
     return vec;
}

or would it be problematic because vec is a local variable, and SparseVector is not just a smart pointer wrapper class around vec?

Meaning, perhaps it is better to use:

SparseVector<double>* retVector()
{
     SparseVector<Double> *vec = new SparseVector<double>(3);
     return vec;
}
1

There are 1 answers

0
Avi Ginsburg On

It's more complicated than that. If we were talking about any object, @Anycorn is correct in that the unoptimized version will make a deep copy of object in the function upon return. However, with optimizations enabled copy elision allows the compiler to construct the returned object in place. This is not specific to Eigen. As an example, we can look at a function similar to yours that returns an Eigen object.

Eigen::MatrixXd retMat()
{
    Eigen::MatrixXd inMat = Eigen::MatrixXd::Random(1000,1000);
    std::cout << &inMat << "\t" << inMat.data() << "\t" << *(inMat.data() + rand() * 20) <<  "\n";
    return inMat;
}

Eigen::SparseMatrix<double> retSMat()
{
    Eigen::MatrixXd denseMat = Eigen::MatrixXd::Random(100,100);
    Eigen::SparseMatrix<double> inMat = denseMat.sparseView();
    std::cout << &inMat << "\t" <<  "\n";

    return inMat;
}

int main(int argc, char *argv[])
{
    srand(time(NULL));
    Eigen::MatrixXd outMat(5,5);
    std::cout << "Dense matrix addresses (1):\n";
    std::cout << &outMat << "\t" << outMat.data() << "\n";
    outMat = retMat();
    std::cout << &outMat << "\t" << outMat.data() << "\n\n";

    std::cout << "Dense matrix addresses (2):\n";
    Eigen::MatrixXd outMat2 = retMat();
    std::cout << &outMat2 << "\t" << outMat2.data() << "\n\n";

    std::cout << "Sparse matrix addresses:\n";
    Eigen::SparseMatrix<double> outMatSp = retSMat();
    std::cout << &outMatSp << "\n";
    return 0;
}

Run in Debug (optimizations turned off) we get:

Dense matrix addresses (1):
00000032B6A8EF58        00000032B6BDB870
00000032B6A8EEA8        00000032B6CDA070        -0.620289
00000032B6A8EF58        00000032B6CDD070

Dense matrix addresses (2):
00000032B6A8EEA8        00000032B7481070        0.157872
00000032B6A8EF88        00000032B7C3F070

Sparse matrix addresses:
00000032B6A8EE60
00000032B6A8EFC0

We see that none of the addresses are identical, as would be expected by a naive interpretation of the code. If optimizations are turned on, the picture is a little different:

Dense matrix addresses (1):
0000009BEB0AF700        0000009BEB14BBF0
0000009BEB0AF780        0000009BEB2C1040        0.862606
0000009BEB0AF700        0000009BEBA7B040

Dense matrix addresses (2):
0000009BEB0AF718        0000009BEB2CC040        -0.601367
0000009BEB0AF718        0000009BEB2CC040

Sparse matrix addresses:
0000009BEB0AF740
0000009BEB0AF740

We see that in case (2) and the sparse matrix example, the addresses of the objects are identical, indicating that the object was created in the target objects address. Note that this is different than move semantics, which require a written move constructor (I'm pretty sure Eigen does not yet have an implemented one).

Regarding Eigen, due to its lazy evaluation, some expressions are not evaluated immediately, but rather when deemed prudent/necessary. This is is not the case in the simple example, but can be if the example had some calculations in it. In such a case, the returned object might be an expression tree that is appended/evaluated in the resulting object.