Eigen performance gotcha calling non-templated function from templated one

Alec Jacobson

July 25, 2017

weblog/

I just spent a while tracking down a rather surprising performance bug in my code.

Here's a minimal example:

#include <Eigen/Dense>
#include <iostream>

int simple_size(const Eigen::MatrixXi & Z)
{
  return Z.size();
}

template <typename T> int templated_size(const Eigen::MatrixBase<T> & Y)
{
  return simple_size(Y);
}

int main(int argc, const char * argv[])
{
  const int s = 40000;
  Eigen::MatrixXi X = Eigen::MatrixXi::Zero(40000,40000);
  std::cout<<"Compare:"<<std::endl;
  std::cout<<(X.size()         ?"done":"")<<std::endl;
  std::cout<<(simple_size(X)          ?"done":"")<<std::endl;
  std::cout<<(templated_size(X)?"done":"")<<std::endl;

}

Running this, it will show that the last call to templated_size is taking way too long. Inspection will show that a copy of Y is being created to create a Eigen::MatrixXi & reference.

Now, clearly it's poor design to call a function expecting a Eigen::MatrixXi & reference with a generic templated type Eigen::MatrixBase<T> &, but unfortunately this happens quite often with legacy libigl functions. My expectation was that since T is Eigen::MatrixXi in this case a simple reference would be passed.

It's worth noting that const is actually creating/hiding the problem. Because simple_size takes a const reference, the compiler is happy to construct a Eigen::MatrixXi on the fly to create a valid reference. Without the consts the compiler stops at an error.