Table of Contents

  1. Header-Only Integration
  2. FetchContent vs find_package
  3. SIMD & Vectorization Flags
  4. Eigen with BLAS Backend
  5. Compile-Time Optimization
  6. Eigen and CUDA
  7. Common Issues
Back to CMake Mastery Series

Eigen

June 4, 2026 Wasil Zafar 8 min read

Integrate Eigen — the premier C++ template library for linear algebra — with CMake, from basic header-only setup to SIMD-optimized high-performance numerical computing.

Math

Header-Only Integration

Eigen is entirely header-only — no compiled libraries are involved. It ships CMake config files that define an imported target Eigen3::Eigen:

# Standard Eigen3 discovery
cmake_minimum_required(VERSION 3.20)
project(MathApp LANGUAGES CXX)

find_package(Eigen3 3.4 REQUIRED NO_MODULE)

add_executable(math_app main.cpp)
target_link_libraries(math_app PRIVATE Eigen3::Eigen)
Key Insight: The NO_MODULE flag forces config-mode search, skipping any old FindEigen3.cmake files that may produce incorrect results. Eigen's own Eigen3Config.cmake is always preferred.
// main.cpp — Basic Eigen usage
#include <Eigen/Dense>
#include <iostream>

int main() {
    // Create a 3x3 matrix
    Eigen::Matrix3d A;
    A << 1, 2, 3,
         4, 5, 6,
         7, 8, 10;

    // Create a vector
    Eigen::Vector3d b(3, 3, 4);

    // Solve linear system Ax = b
    Eigen::Vector3d x = A.colPivHouseholderQr().solve(b);

    std::cout << "Solution:\n" << x << "\n";
    std::cout << "Verification (A*x):\n" << A * x << "\n";

    return 0;
}

FetchContent vs find_package

For projects that need a pinned Eigen version or work in environments without system Eigen, FetchContent provides a clean solution:

# FetchContent approach — download Eigen at configure time
cmake_minimum_required(VERSION 3.24)
project(EigenFetch LANGUAGES CXX)

include(FetchContent)

FetchContent_Declare(eigen
    GIT_REPOSITORY https://gitlab.com/libeigen/eigen.git
    GIT_TAG 3.4.0
    GIT_SHALLOW ON
    FIND_PACKAGE_ARGS 3.4 NO_MODULE  # Try system first
)
FetchContent_MakeAvailable(eigen)

add_executable(math_app main.cpp)
target_link_libraries(math_app PRIVATE Eigen3::Eigen)

The FIND_PACKAGE_ARGS option (CMake 3.24+) makes FetchContent try find_package(Eigen3 3.4 NO_MODULE) first and only downloads if that fails — giving you the best of both worlds.

# Alternative: ExternalProject for full isolation
include(ExternalProject)

ExternalProject_Add(eigen_download
    GIT_REPOSITORY https://gitlab.com/libeigen/eigen.git
    GIT_TAG 3.4.0
    CMAKE_ARGS
        -DCMAKE_INSTALL_PREFIX=<INSTALL_DIR>
        -DBUILD_TESTING=OFF
        -DEIGEN_BUILD_DOC=OFF
    BUILD_COMMAND ""  # Header-only: nothing to build
)

# After install, set Eigen3_DIR for find_package
ExternalProject_Get_Property(eigen_download INSTALL_DIR)
set(Eigen3_DIR "${INSTALL_DIR}/share/eigen3/cmake")

SIMD & Vectorization Flags

Eigen auto-detects and uses SIMD instruction sets (SSE, AVX, AVX2, AVX-512, NEON) at compile time. The compiler flags determine which instructions are available:

# SIMD optimization flags
cmake_minimum_required(VERSION 3.20)
project(EigenSIMD LANGUAGES CXX)

find_package(Eigen3 3.4 REQUIRED NO_MODULE)

add_executable(simd_app main.cpp)
target_link_libraries(simd_app PRIVATE Eigen3::Eigen)

# Enable maximum SIMD for the build machine
if(CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang")
    target_compile_options(simd_app PRIVATE -march=native)
elseif(MSVC)
    target_compile_options(simd_app PRIVATE /arch:AVX2)
endif()

# For portable binaries, specify a baseline
# target_compile_options(simd_app PRIVATE -march=x86-64-v3)  # AVX2 baseline
Pitfall — Mixing SIMD Levels: If different translation units are compiled with different -march flags, Eigen may use different alignment and vectorization in each, causing crashes at link time. Always set SIMD flags uniformly across all sources that include Eigen headers.
# Detect available SIMD and report
include(CheckCXXCompilerFlag)
check_cxx_compiler_flag("-mavx2" COMPILER_SUPPORTS_AVX2)
check_cxx_compiler_flag("-mavx512f" COMPILER_SUPPORTS_AVX512)

if(COMPILER_SUPPORTS_AVX512)
    message(STATUS "Eigen: AVX-512 available")
    target_compile_options(simd_app PRIVATE -mavx512f -mavx512dq)
elseif(COMPILER_SUPPORTS_AVX2)
    message(STATUS "Eigen: AVX2 available")
    target_compile_options(simd_app PRIVATE -mavx2 -mfma)
endif()

Eigen with BLAS Backend

For large matrix operations, Eigen can delegate to optimized BLAS/LAPACK implementations (OpenBLAS, MKL, Accelerate) for significantly better performance:

# Eigen with OpenBLAS backend
cmake_minimum_required(VERSION 3.20)
project(EigenBLAS LANGUAGES CXX)

find_package(Eigen3 3.4 REQUIRED NO_MODULE)
find_package(BLAS REQUIRED)
find_package(LAPACK REQUIRED)

add_executable(blas_app main.cpp)
target_link_libraries(blas_app PRIVATE
    Eigen3::Eigen
    ${BLAS_LIBRARIES}
    ${LAPACK_LIBRARIES}
)

# Tell Eigen to use BLAS backend for large operations
target_compile_definitions(blas_app PRIVATE
    EIGEN_USE_BLAS
    EIGEN_USE_LAPACKE
)
// main.cpp — Eigen with BLAS acceleration
#define EIGEN_USE_BLAS
#define EIGEN_USE_LAPACKE
#include <Eigen/Dense>
#include <iostream>
#include <chrono>

int main() {
    const int N = 1000;

    // Large matrix multiplication — delegated to BLAS
    Eigen::MatrixXd A = Eigen::MatrixXd::Random(N, N);
    Eigen::MatrixXd B = Eigen::MatrixXd::Random(N, N);

    auto start = std::chrono::high_resolution_clock::now();
    Eigen::MatrixXd C = A * B;  // Uses BLAS dgemm internally
    auto end = std::chrono::high_resolution_clock::now();

    double ms = std::chrono::duration<double, std::milli>(end - start).count();
    std::cout << N << "x" << N << " multiply: " << ms << " ms\n";
    std::cout << "GFLOPS: " << (2.0 * N * N * N) / (ms * 1e6) << "\n";

    return 0;
}

Compile-Time Optimization

Eigen is heavily template-based, which can slow compilation. These strategies reduce build times:

# Strategies to reduce Eigen compilation time
cmake_minimum_required(VERSION 3.20)
project(EigenFast LANGUAGES CXX)

find_package(Eigen3 3.4 REQUIRED NO_MODULE)

add_executable(fast_app main.cpp math_utils.cpp)
target_link_libraries(fast_app PRIVATE Eigen3::Eigen)

# 1. Disable debug assertions in release builds
target_compile_definitions(fast_app PRIVATE
    $<$<CONFIG:Release>:EIGEN_NO_DEBUG>
    $<$<CONFIG:Release>:NDEBUG>
)

# 2. Limit template instantiation depth
if(CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang")
    target_compile_options(fast_app PRIVATE
        -ftemplate-depth=512
    )
endif()

# 3. Use precompiled headers (CMake 3.16+)
target_precompile_headers(fast_app PRIVATE
    <Eigen/Dense>
    <Eigen/Sparse>
)
Precompiled Headers: Adding <Eigen/Dense> to a PCH can reduce compile times by 30-50% in projects with many translation units that use Eigen, since the template-heavy headers are parsed only once.

Eigen and CUDA

Eigen supports CUDA device code, allowing you to use Eigen matrices inside GPU kernels:

# Eigen with CUDA support
cmake_minimum_required(VERSION 3.20)
project(EigenCUDA LANGUAGES CXX CUDA)

find_package(Eigen3 3.4 REQUIRED NO_MODULE)

add_executable(eigen_cuda main.cu)
target_link_libraries(eigen_cuda PRIVATE Eigen3::Eigen)

# Required for Eigen in device code
target_compile_options(eigen_cuda PRIVATE
    $<$<COMPILE_LANGUAGE:CUDA>:--expt-relaxed-constexpr>
)

set_target_properties(eigen_cuda PROPERTIES
    CUDA_SEPARABLE_COMPILATION ON
    CUDA_ARCHITECTURES "70;80;86"
)
// main.cu — Eigen inside CUDA kernels
#include <Eigen/Dense>
#include <iostream>

__global__ void transform_kernel(float* output, int n) {
    int idx = blockIdx.x * blockDim.x + threadIdx.x;
    if (idx >= n) return;

    // Use Eigen types on device
    Eigen::Vector3f v(1.0f, 2.0f, 3.0f);
    Eigen::Matrix3f rot;
    rot = Eigen::AngleAxisf(0.1f * idx, Eigen::Vector3f::UnitZ());

    Eigen::Vector3f result = rot * v;
    output[idx * 3 + 0] = result.x();
    output[idx * 3 + 1] = result.y();
    output[idx * 3 + 2] = result.z();
}

int main() {
    const int N = 1024;
    float* d_output;
    cudaMalloc(&d_output, N * 3 * sizeof(float));

    transform_kernel<<<(N + 255) / 256, 256>>>(d_output, N);
    cudaDeviceSynchronize();

    std::vector<float> h_output(N * 3);
    cudaMemcpy(h_output.data(), d_output, N * 3 * sizeof(float), cudaMemcpyDeviceToHost);

    std::cout << "First result: (" << h_output[0] << ", "
              << h_output[1] << ", " << h_output[2] << ")\n";

    cudaFree(d_output);
    return 0;
}

Common Issues

Issue #1 — Alignment Crashes: Eigen uses 16/32-byte alignment for SIMD. Placing Eigen types in STL containers or classes allocated with new can segfault. Use EIGEN_MAKE_ALIGNED_OPERATOR_NEW in classes or Eigen::aligned_allocator for STL containers.
// Fix: Aligned allocator for STL containers
#include <Eigen/Dense>
#include <Eigen/StdVector>
#include <vector>

// Safe: aligned allocator
std::vector<Eigen::Vector4f, Eigen::aligned_allocator<Eigen::Vector4f>> points;

// Or in C++17+, use std::vector directly (guaranteed aligned new)
// std::vector<Eigen::Vector4f> points;  // OK in C++17
Issue #2 — Config Not Found: If find_package(Eigen3) fails, Eigen may be installed without its CMake config. Set Eigen3_DIR explicitly:
# Manual path to Eigen3Config.cmake
set(Eigen3_DIR "/usr/local/share/eigen3/cmake" CACHE PATH "Eigen3 cmake config")
# Or on Homebrew macOS:
# set(Eigen3_DIR "/opt/homebrew/share/eigen3/cmake")

find_package(Eigen3 3.4 REQUIRED NO_MODULE)
Best Practices Summary:
  • Always use NO_MODULE with find_package(Eigen3)
  • Use -march=native for development, explicit ISA for releases
  • Enable EIGEN_USE_BLAS for matrices larger than ~64×64
  • Add <Eigen/Dense> to precompiled headers for faster builds
  • Define EIGEN_NO_DEBUG in release builds for performance
  • Use FetchContent with FIND_PACKAGE_ARGS for maximum portability