c++matrixc++17deep-copyeigen3

In Eigen, for a preallocated matrix, does the assignment operator try to reuse existing memory?


Consider the following Eigen code snippets:

In option 1, I assume B starts off having size (0, 0), and then gets dynamically allocated to have a size that matches that of A, and then A's elements are copied into B. My questions are:

  1. In option 2, since B is already allocated dynamically with a size that matches that of A, is Eigen able to just copy the elements of A over to those of B? Or does it do something along the lines of a delete followed by a malloc/new of the appropriate size, discarding the preallocated memory?
  2. If the latter is true (i.e., B gets deleted and reallocated anyway), then what's an efficient / elegant way to copy over A's elements to B while avoiding memory reallocation, if I know that B will have the correct size already? I could just write loops over m and n and do a manual copy, but I'm wondering if there are in-built approaches that can take advantage of vectorization. Approaches I've considered:
    • Get the underlying data pointer for A and then use Eigen::Map, but I don't think that actually copies the data over - it just points B to the memory block associated with A.
    • .noalias() won't work because it appears not to have an effect here, since it applies to matrix multiplications only, according to the docs.
    • One way to do it could be:
    Eigen::MatrixXf A (m, n), B (m, n);
    B.block(0, 0, m, n) = A;
    
    but I'm wondering if there's a better way.

Solution

  • TLDR

    does the assignment operator try to reuse existing memory?

    Yes. Just write B = A and go on with your life.

    Explanation and corner cases

    Assignment (except move assignment, when appropriate) is handled in two steps.

    1. Call resize(target_rows, target_cols)
    2. Copy elements over

    If the current size is the same as the target size, no new allocation is made. This is important to keep in mind when the left side aliases the right side of the assignment. The following code works as expected because old values are read in the same order they are overwritten:
    A = A * 2.f.
    This does not because some values are read after being overwritten:
    A.bottomRows(N) = A.topRows(N) * 2.f (with N > A.rows() / 2)

    If the current size differs AND the matrix size is fixed, e.g. Matrix4f, then an assertion is triggered. If assertions are disabled, undefined behavior follows. The same logic applies to block() expressions and similar, since they cannot resize the underlying matrix.

    If the size differs and the matrix size is dynamic (in the relevant dimension), a new allocation is made that is initially filled with undefined values. Compare conservativeResize vs. resize.

    This too can lead to a nasty surprise with regard to aliasing in cases like
    A = A.topRows(N) with N < A.rows().
    In this case the right side references the old allocation but the assignment operator deallocates that before trying to assign to the new allocation, causing a dangling pointer dereference. You can work around this by forcing a new allocation first with
    A = A.topRows(N).eval().
    eval() generates a new matrix which will then be move-assigned to A.

    As you have noted yourself, matrix multiplications are an exception and always assume that the left side aliases the right side of the assignment and create a new allocation unless you write A.noalias() = B * C. A few other operations also have special cases for aliasing, in particular PermutationMatrix optimizes the cases
    A = Permutation * A and A = A * Permutation