| // SPDX-FileCopyrightText: The Eigen Authors | |
| // SPDX-License-Identifier: MPL-2.0 | |
| // KroneckerOperator products and solves, (A (x) B) vec(X) = vec(B X A^T), with | |
| // n x n factors and r right-hand sides. The product GFLOPS count | |
| // 2 r (work(B) n1 + m2 work(A)) flops, work(F) = stored entries of F. Each | |
| // benchmark first checks its result outside the timed loop. | |
| #include <benchmark/benchmark.h> | |
| #include <Eigen/Dense> | |
| #include <Eigen/Sparse> | |
| #include <contrib/Eigen/StructuredMatrices> | |
| using namespace Eigen; | |
| using Mat = MatrixXd; | |
| using SpMat = SparseMatrix<double>; | |
| using Diag = DiagonalMatrix<double, Dynamic>; | |
| namespace { | |
| // The 1-D Laplacian tridiag(-1, 2, -1): its Kronecker sums are the 2-D | |
| // finite-difference operators. | |
| SpMat laplacian(Index n) { | |
| SpMat L(n, n); | |
| L.reserve(VectorXi::Constant(n, 3)); | |
| for (Index j = 0; j < n; ++j) { | |
| if (j > 0) L.insert(j - 1, j) = -1.0; | |
| L.insert(j, j) = 2.0; | |
| if (j + 1 < n) L.insert(j + 1, j) = -1.0; | |
| } | |
| L.makeCompressed(); | |
| return L; | |
| } | |
| Mat dominant(Index n) { return Mat::Random(n, n) + double(2 * n) * Mat::Identity(n, n); } | |
| void setFlops(benchmark::State& state, double flopsPerIteration) { | |
| state.counters["GFLOPS"] = | |
| benchmark::Counter(1e-9 * flopsPerIteration, benchmark::Counter::kIsIterationInvariantRate); | |
| } | |
| // The batched result must match the operator applied to one column at a time. | |
| template <typename Lhs, typename Rhs> | |
| bool productMatchesColumns(const KroneckerOperator<Lhs, Rhs>& K, const Mat& X, const Mat& Y) { | |
| Mat expected(K.rows(), X.cols()); | |
| for (Index k = 0; k < X.cols(); ++k) expected.col(k) = K * X.col(k); | |
| return (Y - expected).norm() <= 1e-10 * expected.norm(); | |
| } | |
| // K x = b up to the conditioning of the (diagonally dominant) factors. | |
| template <typename Lhs, typename Rhs> | |
| bool solveHasSmallResidual(const KroneckerOperator<Lhs, Rhs>& K, const Mat& x, const Mat& b) { | |
| const Mat residual = K * x - b; | |
| return residual.norm() <= 1e-8 * b.norm(); | |
| } | |
| template <typename Lhs, typename Rhs> | |
| void runProduct(benchmark::State& state, const KroneckerOperator<Lhs, Rhs>& K, double workA, double workB) { | |
| const Index r = state.range(1); | |
| const Mat X = Mat::Random(K.cols(), r); | |
| Mat Y = K * X; | |
| if (!productMatchesColumns(K, X, Y)) { | |
| state.SkipWithError("batched product differs from the column-by-column product"); | |
| return; | |
| } | |
| for (auto _ : state) { | |
| Y.noalias() = K * X; | |
| benchmark::DoNotOptimize(Y.data()); | |
| benchmark::ClobberMemory(); | |
| } | |
| const double n1 = double(K.lhs().cols()), m2 = double(K.rhs().rows()); | |
| setFlops(state, 2.0 * double(r) * (workB * n1 + m2 * workA)); | |
| } | |
| void BM_ProductDense(benchmark::State& state) { | |
| const Index n = state.range(0); | |
| const KroneckerOperator<Mat, Mat> K(Mat::Random(n, n), Mat::Random(n, n)); | |
| runProduct(state, K, double(n * n), double(n * n)); | |
| } | |
| void BM_ProductSparse(benchmark::State& state) { | |
| const Index n = state.range(0); | |
| const SpMat L = laplacian(n); | |
| const KroneckerOperator<SpMat, SpMat> K(L, L); | |
| runProduct(state, K, double(L.nonZeros()), double(L.nonZeros())); | |
| } | |
| // I_n (x) B, the block-diagonal operator of n independent n x n systems. | |
| void BM_ProductIdentityDense(benchmark::State& state) { | |
| const Index n = state.range(0); | |
| const KroneckerOperator<Diag, Mat> K(Diag(VectorXd::Ones(n)), Mat::Random(n, n)); | |
| runProduct(state, K, double(n), double(n * n)); | |
| } | |
| // L (x) I_n and I_n (x) L: a sparse factor against a diagonal one. | |
| void BM_ProductSparseIdentity(benchmark::State& state) { | |
| const SpMat L = laplacian(state.range(0)); | |
| const KroneckerOperator<SpMat, Diag> K(L, Diag(VectorXd::Ones(L.rows()))); | |
| runProduct(state, K, double(L.nonZeros()), double(L.rows())); | |
| } | |
| void BM_ProductIdentitySparse(benchmark::State& state) { | |
| const SpMat L = laplacian(state.range(0)); | |
| const KroneckerOperator<Diag, SpMat> K(Diag(VectorXd::Ones(L.rows())), L); | |
| runProduct(state, K, double(L.rows()), double(L.nonZeros())); | |
| } | |
| template <typename Lhs, typename Rhs> | |
| void runSolve(benchmark::State& state, const KroneckerOperator<Lhs, Rhs>& K) { | |
| const Mat b = Mat::Random(K.rows(), state.range(1)); | |
| Mat x = K.solve(b); | |
| if (!solveHasSmallResidual(K, x, b)) { | |
| state.SkipWithError("solve residual too large"); | |
| return; | |
| } | |
| for (auto _ : state) { | |
| x = K.solve(b); | |
| benchmark::DoNotOptimize(x.data()); | |
| benchmark::ClobberMemory(); | |
| } | |
| } | |
| void BM_SolveDense(benchmark::State& state) { | |
| const Index n = state.range(0); | |
| runSolve(state, KroneckerOperator<Mat, Mat>(dominant(n), dominant(n))); | |
| } | |
| void BM_SolveSparse(benchmark::State& state) { | |
| const SpMat L = laplacian(state.range(0)); | |
| runSolve(state, KroneckerOperator<SpMat, SpMat>(L, L)); | |
| } | |
| void BM_LeastSquaresDense(benchmark::State& state) { | |
| const Index n = state.range(0); | |
| const KroneckerOperator<Mat, Mat> K(dominant(n), dominant(n)); // full rank: least squares is the solve | |
| const Mat b = Mat::Random(K.rows(), state.range(1)); | |
| Mat x = K.leastSquaresSolve(b); | |
| if (!solveHasSmallResidual(K, x, b)) { | |
| state.SkipWithError("least-squares residual too large"); | |
| return; | |
| } | |
| for (auto _ : state) { | |
| x = K.leastSquaresSolve(b); | |
| benchmark::DoNotOptimize(x.data()); | |
| benchmark::ClobberMemory(); | |
| } | |
| } | |
| } // namespace | |
| BENCHMARK(BM_ProductDense)->ArgsProduct({{8, 32, 128}, {1, 32}}); | |
| BENCHMARK(BM_ProductSparse)->ArgsProduct({{64, 512}, {1, 32}}); | |
| BENCHMARK(BM_ProductIdentityDense)->ArgsProduct({{8, 32, 128}, {1, 32}}); | |
| BENCHMARK(BM_ProductSparseIdentity)->ArgsProduct({{64, 512}, {1, 32}}); | |
| BENCHMARK(BM_ProductIdentitySparse)->ArgsProduct({{64, 512}, {1, 32}}); | |
| BENCHMARK(BM_SolveDense)->ArgsProduct({{8, 32, 128}, {1, 32}}); | |
| BENCHMARK(BM_SolveSparse)->ArgsProduct({{64, 512}, {1, 32}}); | |
| BENCHMARK(BM_LeastSquaresDense)->ArgsProduct({{32}, {1, 32}}); |