blob: 1c2c84dcea956b969c130816cf7916331618140d [file]
// 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