Eigen3 Benchmarks

Matrix Inversion using Eigen C++ Library | January 28, 2018


  • cpp
  • eigen3
  • spdlog
  • conan

Eigen is super fast linear algebra library for C++. It provides almost all matrix / vector related operations and some extra pandas / numpy style functionality. Recently, one of my colleagues was looking for a linear algebra for C++ and I suggested using Eigen. During our conversation, we were discussing how fast are matrix inverse operation in Eigen, however the Eigen docs did not provide a satisfactory benchmarks for inversion. So, I decided to do a little test on my own.

TL;DR Version

Matrix Size Mean (in ns) Min (in ns) Max (in ns)
1 1094.94 0 1.824e+06
2 1033.88 0 66000
3 1172.07 1000 99000
4 1262.35 1000 151000
5 1573.22 1000 134000
6 1747.54 1000 489000
7 1977.85 1000 51000
8 2076.58 1000 1.637e+06
9 2455.33 2000 110000
10 2759.75 2000 749000
11 3265.28 3000 209000
12 3354.84 3000 139000
13 4417.52 3000 3.187e+06
14 4542.06 4000 61000
15 5553.37 5000 462000
16 5469.77 5000 115000
17 7014.22 6000 788000
18 7344.36 6000 417000
19 8805.54 8000 184000
20 8824.95 8000 102000
21 10390.2 9000 2.865e+06
22 11314.2 10000 343000
23 13065.2 12000 728000
24 12958.2 12000 189000
25 15338.9 14000 1.149e+06

For the Curious Ones

I have uploaded the cmake project for this test on my Github. If you want to explore and play around with the project, please keep reading.

In this project, I have also used conan, which is a python-based open-source C++ package manager. Recently, conan community released the 1.0 version of the package. We can install it by simply using pip.

pip install conan

After instaling conan, you will need to add a couple of conan repositories.

conan remote add conan-community "https://api.bintray.com/conan/conan-community/conan" # For spdlog
conan remote add bincrafters https://api.bintray.com/conan/bincrafters/public-conan # For Eigen3

After adding the repositories, we can setup a conanfile.txt in the root directory of the project.

[requires]
eigen/3.3.4@conan/stable
spdlog/0.14.0@bincrafters/stable

[generators]
cmake

conan will generate a .cmake file, which will have paths to all the libraries that we use in our project.

On my Mac, I use the latest gcc 7.3 installed from Homebrew.

brew install gcc

If Homebrew gcc is already installed, we can upgrade it using:

brew upgrade gcc

Below is how my CMakeLists.txt file looks like.


cmake_minimum_required(VERSION 3.6)
set(CMAKE_CXX_STANDARD 17)

file(COPY ${CMAKE_SOURCE_DIR}/conanfile.txt DESTINATION .)

execute_process(COMMAND conan install . --settings os=Macos -g cmake --profile gcc RESULT_VARIABLE CONAN_EXIT_CODE)

include(${CMAKE_CURRENT_BINARY_DIR}/conanbuildinfo.cmake)
set(CMAKE_VERBOSE_MAKEFILE TRUE)

if (APPLE)
    set(GCC_ROOT_DIR /usr/local/bin)

    set(CMAKE_C_COMPILER   ${GCC_ROOT_DIR}/gcc-7)
    set(CMAKE_CXX_COMPILER ${GCC_ROOT_DIR}/g++-7)
    set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++17")
endif()

project(eigen-benchmarks)

conan_define_targets()

include_directories(${CONAN_INCLUDE_DIRS_EIGEN})
include_directories(${CONAN_INCLUDE_DIRS_SPDLOG})

set(SOURCE_FILES main.cpp)
add_executable(eigen-benchmarks ${SOURCE_FILES})
install(TARGETS eigen-benchmarks DESTINATION ${CMAKE_CURRENT_BINARY_DIR})

The main program is below:

#include <iostream>
#include "numeric"
#include "chrono"
#include <time.h>
#include <Eigen/Dense>
#include "spdlog/spdlog.h"
#include "spdlog/fmt/bundled/ostream.h"

namespace spd = spdlog;
constexpr int MAX_MATRIX_SIZE = 25;
constexpr int TOTAL_ITERATIONS = 100000;
constexpr int IGNORE_COUNT = 10;

double_t calc_time(int32_t size);

int main()
{
    srand(static_cast<unsigned int>(time(nullptr)));
    auto console = spd::stdout_color_mt("console");
    console->info("++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++");
    console->info("Eigen Benchmarks (in ns)");
    console->info("++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++");

    std::map<int32_t, double_t> averages;
    std::map<int32_t, double_t> maxs;
    std::map<int32_t, double_t> mins;

    std::cout << "Matrix Size | Mean | Min | Max" << std::endl;
    std::cout << "------------|------|-----|----" << std::endl;

    for (int i = 0; i < MAX_MATRIX_SIZE; ++i)
    {
        std::vector<double_t> timings;
        for (int j = 0; j < TOTAL_ITERATIONS; ++j)
        {
            if (j >= IGNORE_COUNT) {
                auto time_taken = calc_time(i + 1);
                timings.push_back(time_taken);
            }
        }

        auto avg = (std::accumulate(timings.begin(), timings.end(), 0.0) / timings.size());
        auto minimum = *std::min_element(std::begin(timings), std::end(timings));
        auto maximum = *std::max_element(std::begin(timings), std::end(timings));

        std::cout << i + 1 << " | " << avg << " | " << minimum << " | " << maximum << std::endl;
    }

    console->info("++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++");
}

double_t calc_time(int32_t size)
{
    Eigen::MatrixXd m = Eigen::MatrixXd::Random(size, size);
    auto invertible = m * m.transpose();
    auto begin = std::chrono::steady_clock::now();
    Eigen::MatrixXd m_inv = m.inverse();
    auto end = std::chrono::steady_clock::now();
    auto time_diff = end - begin;
    auto time_taken = static_cast<double_t>(
            std::chrono::duration_cast<std::chrono::nanoseconds>(time_diff).count());
    return time_taken;
}

Phone

+1-312-566-7843

Address

------------------------
Chicago, IL 60605
United States of America