| // SPDX-FileCopyrightText: The Eigen Authors |
| // SPDX-License-Identifier: MPL-2.0 |
| |
| #include <benchmark/benchmark.h> |
| #include <Eigen/SVD> |
| |
| #include <algorithm> |
| #include <cmath> |
| #include <limits> |
| #include <random> |
| #include <vector> |
| |
| #ifndef SCALAR |
| #define SCALAR double |
| #endif |
| |
| using Scalar = SCALAR; |
| using Matrix = Eigen::Matrix<Scalar, Eigen::Dynamic, Eigen::Dynamic>; |
| #ifdef EIGEN_BENCH_BDCSVD |
| using Solver = Eigen::BDCSVD<Matrix, Eigen::ComputeThinU | Eigen::ComputeThinV>; |
| #else |
| using Solver = Eigen::JacobiSVD<Matrix, Eigen::ComputeThinU | Eigen::ComputeThinV>; |
| #endif |
| |
| static void BM_SVDScaling(benchmark::State& state) { |
| const Eigen::Index rows = state.range(0); |
| const Eigen::Index cols = state.range(1); |
| const int scaleKind = int(state.range(2)); |
| const int exponent = scaleKind == 1 ? std::numeric_limits<Scalar>::max_exponent - 8 |
| : scaleKind == 2 ? std::numeric_limits<Scalar>::min_exponent + 4 |
| : 0; |
| const Scalar scale = std::ldexp(Scalar(1), exponent); |
| constexpr int kBatchSize = 8; |
| std::mt19937 random(12345); |
| std::vector<Matrix> inputs; |
| inputs.reserve(kBatchSize); |
| Solver solver(rows, cols); |
| const Scalar tolerance = Scalar(32 * (std::max)(rows, cols)) * Eigen::NumTraits<Scalar>::epsilon(); |
| for (int sample = 0; sample < kBatchSize; ++sample) { |
| Matrix reference(rows, cols); |
| for (Eigen::Index i = 0; i < reference.size(); ++i) { |
| reference.data()[i] = Scalar(int(random() % 2001) - 1000) / Scalar(1024); |
| } |
| inputs.emplace_back(reference * scale); |
| benchmark::DoNotOptimize(inputs.back().data()); |
| benchmark::ClobberMemory(); |
| solver.compute(inputs.back()); |
| if (solver.info() != Eigen::Success) { |
| state.SkipWithError("SVD did not converge"); |
| return; |
| } |
| // Restore in normalized coordinates so validation itself cannot overflow or underflow. |
| const Matrix reconstruction = |
| solver.matrixU() * (solver.singularValues() / scale).asDiagonal() * solver.matrixV().adjoint(); |
| const Scalar residual = (reconstruction - reference).norm() / reference.norm(); |
| const Eigen::Index rank = (std::min)(rows, cols); |
| const Matrix identity = Matrix::Identity(rank, rank); |
| const Scalar uError = (solver.matrixU().adjoint() * solver.matrixU() - identity).norm(); |
| const Scalar vError = (solver.matrixV().adjoint() * solver.matrixV() - identity).norm(); |
| if (!(residual <= tolerance && uError <= tolerance && vError <= tolerance)) { |
| state.SkipWithError("SVD reconstruction or orthogonality check failed"); |
| return; |
| } |
| } |
| state.SetLabel(scaleKind == 0 ? "ordinary" : scaleKind == 1 ? "large" : "tiny"); |
| // Every timed batch visits the same matrix pool, independent of iteration-count calibration. |
| while (state.KeepRunningBatch(kBatchSize)) { |
| for (const Matrix& input : inputs) { |
| benchmark::DoNotOptimize(input.data()); |
| benchmark::ClobberMemory(); |
| solver.compute(input); |
| benchmark::DoNotOptimize(solver.singularValues().data()); |
| benchmark::DoNotOptimize(solver.matrixU().data()); |
| benchmark::DoNotOptimize(solver.matrixV().data()); |
| benchmark::ClobberMemory(); |
| } |
| } |
| state.SetItemsProcessed(state.iterations()); |
| } |
| |
| #ifdef EIGEN_BENCH_BDCSVD |
| BENCHMARK(BM_SVDScaling) |
| ->Args({32, 32, 0}) |
| ->Args({64, 64, 0}) |
| ->Args({128, 128, 0}) |
| ->Args({256, 32, 0}) |
| ->Args({32, 256, 0}) |
| ->Args({64, 64, 1}) |
| ->Args({64, 64, 2}) |
| ->ArgNames({"rows", "cols", "scale"}); |
| #else |
| BENCHMARK(BM_SVDScaling) |
| ->Args({4, 4, 0}) |
| ->Args({16, 16, 0}) |
| ->Args({32, 32, 0}) |
| ->Args({128, 16, 0}) |
| ->Args({16, 128, 0}) |
| ->Args({32, 32, 1}) |
| ->Args({32, 32, 2}) |
| ->ArgNames({"rows", "cols", "scale"}); |
| #endif |