| // SPDX-FileCopyrightText: The Eigen Authors |
| // SPDX-License-Identifier: MPL-2.0 |
| |
| // Benchmarks the eigenvalues of a random symmetric tridiagonal matrix computed three ways: |
| // - BM_qr_* : the implicit-QR algorithm (SelfAdjointEigenSolver::computeFromTridiagonal), |
| // all eigenvalues; |
| // - BM_bisect_* : Sturm-sequence spectral bisection (TridiagonalEigenSolver), all eigenvalues; |
| // - BM_bisect_sub_* : bisection of only the 10% smallest eigenvalues (an index-range subset, |
| // a query QR cannot answer without computing the whole spectrum). |
| // Suffix _f / _d selects float / double. |
| // |
| // When built with OpenMP, the bisection path parallelizes its independent per-eigenvalue bisections |
| // across Eigen::nbThreads() threads (the QR path is serial). To measure thread scaling, run the |
| // binary once per thread count via the environment, e.g. |
| // for t in 1 2 4 8; do OMP_NUM_THREADS=$t ./bench_tridiagonal_bisection \ |
| // --benchmark_filter='BM_bisect_d/' --benchmark_repetitions=5; done |
| // Do not call Eigen::setNbThreads() to sweep the thread count inside one process: it mutates a global, |
| // so consecutive/interleaved runs at different thread counts overwrite each other's setting and the |
| // measured scaling is meaningless. Keep thread selection in the environment (OMP_NUM_THREADS), one |
| // process per data point. |
| |
| #include <benchmark/benchmark.h> |
| #include <Eigen/Eigenvalues> |
| |
| using namespace Eigen; |
| |
| namespace { |
| |
| enum Mode { kBisectAll, kQrAll, kBisectSubset }; |
| |
| template <typename Scalar> |
| void run(benchmark::State& state, Mode mode) { |
| const Index n = state.range(0); |
| Matrix<Scalar, Dynamic, 1> diag = Matrix<Scalar, Dynamic, 1>::Random(n); |
| Matrix<Scalar, Dynamic, 1> subdiag = Matrix<Scalar, Dynamic, 1>::Random(n > 1 ? n - 1 : 0); |
| |
| if (mode == kQrAll) { |
| SelfAdjointEigenSolver<Matrix<Scalar, Dynamic, Dynamic>> solver(n); |
| for (auto _ : state) { |
| solver.computeFromTridiagonal(diag, subdiag, EigenvaluesOnly); |
| benchmark::DoNotOptimize(solver.eigenvalues().data()); |
| benchmark::ClobberMemory(); |
| } |
| } else { |
| TridiagonalEigenSolver<Scalar> solver(n); |
| const EigenvalueRange range = mode == kBisectSubset ? EigenvalueRange::indices(0, n / 10) : EigenvalueRange::all(); |
| for (auto _ : state) { |
| solver.computeEigenvalues(diag, subdiag, range); |
| benchmark::DoNotOptimize(solver.eigenvalues().data()); |
| benchmark::ClobberMemory(); |
| } |
| } |
| state.SetItemsProcessed(state.iterations() * n); |
| } |
| |
| void BM_qr_f(benchmark::State& s) { run<float>(s, kQrAll); } |
| void BM_bisect_f(benchmark::State& s) { run<float>(s, kBisectAll); } |
| void BM_bisect_sub_f(benchmark::State& s) { run<float>(s, kBisectSubset); } |
| void BM_qr_d(benchmark::State& s) { run<double>(s, kQrAll); } |
| void BM_bisect_d(benchmark::State& s) { run<double>(s, kBisectAll); } |
| void BM_bisect_sub_d(benchmark::State& s) { run<double>(s, kBisectSubset); } |
| |
| // Bisection parallelizes per-eigenvalue, so it keeps scaling to large n; include sizes up to 8192 |
| // (the QR path dominates wall time there but is the right serial baseline). |
| #define EIGEN_BENCH_SIZES ArgsProduct({{16, 32, 64, 128, 256, 512, 1024, 2048, 4096, 8192}}) |
| |
| BENCHMARK(BM_qr_f)->EIGEN_BENCH_SIZES->UseRealTime(); |
| BENCHMARK(BM_bisect_f)->EIGEN_BENCH_SIZES->UseRealTime(); |
| BENCHMARK(BM_bisect_sub_f)->EIGEN_BENCH_SIZES->UseRealTime(); |
| BENCHMARK(BM_qr_d)->EIGEN_BENCH_SIZES->UseRealTime(); |
| BENCHMARK(BM_bisect_d)->EIGEN_BENCH_SIZES->UseRealTime(); |
| BENCHMARK(BM_bisect_sub_d)->EIGEN_BENCH_SIZES->UseRealTime(); |
| |
| } // namespace |