| // Benchmarks for reshaped() views of dense matrices. | |
| // | |
| // Covers the direct-access paths where the Reshaped evaluator serves packets: | |
| // flatten reads, accumulation from a flattened view, reshaped destinations, | |
| // and the AutoOrder flatten of a row-major matrix; plus the expression-sourced | |
| // (non-direct) reshapes that forward the nested evaluator's linear accesses. | |
| // The Map variants perform the same memory operation through Map and serve as | |
| // an upper-bound reference. Each Reshaped benchmark checks its result against | |
| // the Map reference after the timed loop and reports an error on mismatch. | |
| // SPDX-FileCopyrightText: The Eigen Authors | |
| // SPDX-License-Identifier: MPL-2.0 | |
| #include <benchmark/benchmark.h> | |
| #include <Eigen/Core> | |
| using namespace Eigen; | |
| // Flatten a matrix into a vector: dst = src.reshaped(). | |
| template <typename Scalar> | |
| static void BM_ReshapedFlattenRead(benchmark::State& state) { | |
| const Index n = state.range(0); | |
| using Mat = Matrix<Scalar, Dynamic, Dynamic>; | |
| using Vec = Matrix<Scalar, Dynamic, 1>; | |
| Mat src = Mat::Random(n, n); | |
| Vec dst(n * n); | |
| for (auto _ : state) { | |
| dst = src.reshaped(); | |
| benchmark::DoNotOptimize(dst.data()); | |
| } | |
| if (dst != Map<const Vec>(src.data(), src.size())) state.SkipWithError("reshaped flatten differs from Map"); | |
| state.SetBytesProcessed(state.iterations() * n * n * sizeof(Scalar)); | |
| } | |
| // Same flatten through Map: upper-bound reference for BM_ReshapedFlattenRead. | |
| template <typename Scalar> | |
| static void BM_MapFlattenRead(benchmark::State& state) { | |
| const Index n = state.range(0); | |
| using Mat = Matrix<Scalar, Dynamic, Dynamic>; | |
| using Vec = Matrix<Scalar, Dynamic, 1>; | |
| Mat src = Mat::Random(n, n); | |
| Vec dst(n * n); | |
| for (auto _ : state) { | |
| dst = Map<const Vec>(src.data(), src.size()); | |
| benchmark::DoNotOptimize(dst.data()); | |
| } | |
| state.SetBytesProcessed(state.iterations() * n * n * sizeof(Scalar)); | |
| } | |
| // Accumulate a flattened view: dst += alpha * src.reshaped(). | |
| template <typename Scalar> | |
| static void BM_ReshapedFlattenAccumulate(benchmark::State& state) { | |
| const Index n = state.range(0); | |
| using Mat = Matrix<Scalar, Dynamic, Dynamic>; | |
| using Vec = Matrix<Scalar, Dynamic, 1>; | |
| Mat src = Mat::Random(n, n); | |
| Vec dst = Vec::Random(n * n); | |
| const Scalar alpha(2); | |
| for (auto _ : state) { | |
| dst += alpha * src.reshaped(); | |
| benchmark::DoNotOptimize(dst.data()); | |
| } | |
| // The timed dst depends on the iteration count, so check one application from a zero start | |
| // instead; alpha is a power of two, so both paths are exact. | |
| Vec chk = Vec::Zero(n * n); | |
| Vec ref = Vec::Zero(n * n); | |
| chk += alpha * src.reshaped(); | |
| ref += alpha * Map<const Vec>(src.data(), src.size()); | |
| if (chk != ref) state.SkipWithError("reshaped accumulate differs from Map"); | |
| state.SetBytesProcessed(state.iterations() * n * n * sizeof(Scalar)); | |
| } | |
| // Same accumulation through Map: upper-bound reference for BM_ReshapedFlattenAccumulate. | |
| template <typename Scalar> | |
| static void BM_MapFlattenAccumulate(benchmark::State& state) { | |
| const Index n = state.range(0); | |
| using Mat = Matrix<Scalar, Dynamic, Dynamic>; | |
| using Vec = Matrix<Scalar, Dynamic, 1>; | |
| Mat src = Mat::Random(n, n); | |
| Vec dst = Vec::Random(n * n); | |
| const Scalar alpha(2); | |
| for (auto _ : state) { | |
| dst += alpha * Map<const Vec>(src.data(), src.size()); | |
| benchmark::DoNotOptimize(dst.data()); | |
| } | |
| state.SetBytesProcessed(state.iterations() * n * n * sizeof(Scalar)); | |
| } | |
| // Write through a reshaped destination: dst.reshaped() = src. | |
| template <typename Scalar> | |
| static void BM_ReshapedWrite(benchmark::State& state) { | |
| const Index n = state.range(0); | |
| using Mat = Matrix<Scalar, Dynamic, Dynamic>; | |
| using Vec = Matrix<Scalar, Dynamic, 1>; | |
| Mat dst(n, n); | |
| Vec src = Vec::Random(n * n); | |
| for (auto _ : state) { | |
| dst.reshaped() = src; | |
| benchmark::DoNotOptimize(dst.data()); | |
| } | |
| if (Map<const Vec>(dst.data(), dst.size()) != src) state.SkipWithError("reshaped write differs from Map"); | |
| state.SetBytesProcessed(state.iterations() * n * n * sizeof(Scalar)); | |
| } | |
| // AutoOrder flatten of a row-major matrix (vector-shaped reshape with mismatched | |
| // canonical storage order). | |
| template <typename Scalar> | |
| static void BM_ReshapedRowMajorFlatten(benchmark::State& state) { | |
| const Index n = state.range(0); | |
| using Mat = Matrix<Scalar, Dynamic, Dynamic, RowMajor>; | |
| using Vec = Matrix<Scalar, Dynamic, 1>; | |
| Mat src = Mat::Random(n, n); | |
| Vec dst(n * n); | |
| for (auto _ : state) { | |
| dst = src.template reshaped<AutoOrder>(); | |
| benchmark::DoNotOptimize(dst.data()); | |
| } | |
| // AutoOrder flattens in storage order, so dst is the row-major buffer verbatim. | |
| if (dst != Map<const Vec>(src.data(), src.size())) state.SkipWithError("reshaped flatten differs from Map"); | |
| state.SetBytesProcessed(state.iterations() * n * n * sizeof(Scalar)); | |
| } | |
| // Sum-reduce a flattened view. FP reductions are not reassociable by the compiler without | |
| // -ffast-math, so unlike the plain copies above this cannot be rescued by auto-vectorization | |
| // and isolates Eigen's packet path. | |
| template <typename Scalar> | |
| static void BM_ReshapedSum(benchmark::State& state) { | |
| const Index n = state.range(0); | |
| using Mat = Matrix<Scalar, Dynamic, Dynamic>; | |
| using Vec = Matrix<Scalar, Dynamic, 1>; | |
| Mat src = Mat::Random(n, n); | |
| Scalar acc(0); | |
| for (auto _ : state) { | |
| acc += src.reshaped().sum(); | |
| benchmark::DoNotOptimize(acc); | |
| } | |
| // The two paths may sum in different orders (packet vs. scalar redux), so allow rounding | |
| // noise on the order of n*n additions of values in [-1, 1]. This catches garbage results, | |
| // not last-ulp deviations. | |
| const Scalar sum = src.reshaped().sum(); | |
| const Scalar ref = Map<const Vec>(src.data(), src.size()).sum(); | |
| if (numext::abs(sum - ref) > Scalar(16 * n * n) * NumTraits<Scalar>::epsilon()) { | |
| state.SkipWithError("reshaped sum differs from Map"); | |
| } | |
| state.SetBytesProcessed(state.iterations() * n * n * sizeof(Scalar)); | |
| } | |
| // Same reduction through Map: upper-bound reference for BM_ReshapedSum. | |
| template <typename Scalar> | |
| static void BM_MapSum(benchmark::State& state) { | |
| const Index n = state.range(0); | |
| using Mat = Matrix<Scalar, Dynamic, Dynamic>; | |
| using Vec = Matrix<Scalar, Dynamic, 1>; | |
| Mat src = Mat::Random(n, n); | |
| Scalar acc(0); | |
| for (auto _ : state) { | |
| acc += Map<const Vec>(src.data(), src.size()).sum(); | |
| benchmark::DoNotOptimize(acc); | |
| } | |
| state.SetBytesProcessed(state.iterations() * n * n * sizeof(Scalar)); | |
| } | |
| // Elementwise exp of a flattened view. Scalar fallback calls libm per element; the packet | |
| // path uses Eigen's vectorized exp, so this also cannot be rescued by auto-vectorization. | |
| template <typename Scalar> | |
| static void BM_ReshapedCwiseExp(benchmark::State& state) { | |
| const Index n = state.range(0); | |
| using Mat = Matrix<Scalar, Dynamic, Dynamic>; | |
| using Vec = Matrix<Scalar, Dynamic, 1>; | |
| Mat src = Mat::Random(n, n); | |
| Vec dst(n * n); | |
| for (auto _ : state) { | |
| dst = src.reshaped().array().exp(); | |
| benchmark::DoNotOptimize(dst.data()); | |
| } | |
| // Scalar libm exp and packet exp may differ by a few ulps, so compare approximately. | |
| const Vec ref = Map<const Vec>(src.data(), src.size()).array().exp(); | |
| if (!dst.isApprox(ref)) state.SkipWithError("reshaped exp differs from Map"); | |
| state.SetBytesProcessed(state.iterations() * n * n * sizeof(Scalar)); | |
| } | |
| // Same elementwise exp through Map: upper-bound reference for BM_ReshapedCwiseExp. | |
| template <typename Scalar> | |
| static void BM_MapCwiseExp(benchmark::State& state) { | |
| const Index n = state.range(0); | |
| using Mat = Matrix<Scalar, Dynamic, Dynamic>; | |
| using Vec = Matrix<Scalar, Dynamic, 1>; | |
| Mat src = Mat::Random(n, n); | |
| Vec dst(n * n); | |
| for (auto _ : state) { | |
| dst = Map<const Vec>(src.data(), src.size()).array().exp(); | |
| benchmark::DoNotOptimize(dst.data()); | |
| } | |
| state.SetBytesProcessed(state.iterations() * n * n * sizeof(Scalar)); | |
| } | |
| // Flatten an expression source: dst = (a + b).reshaped(). The reshape has no direct access | |
| // and forwards the nested evaluator's linear packets. | |
| template <typename Scalar> | |
| static void BM_ReshapedExprFlattenRead(benchmark::State& state) { | |
| const Index n = state.range(0); | |
| using Mat = Matrix<Scalar, Dynamic, Dynamic>; | |
| using Vec = Matrix<Scalar, Dynamic, 1>; | |
| Mat a = Mat::Random(n, n), b = Mat::Random(n, n); | |
| Vec dst(n * n); | |
| for (auto _ : state) { | |
| dst = (a + b).reshaped(); | |
| benchmark::DoNotOptimize(dst.data()); | |
| } | |
| // Elementwise additions match exactly between the packet and scalar paths. | |
| if (dst != Map<const Vec>(a.data(), a.size()) + Map<const Vec>(b.data(), b.size())) | |
| state.SkipWithError("reshaped expression flatten differs from Map"); | |
| state.SetBytesProcessed(state.iterations() * n * n * sizeof(Scalar)); | |
| } | |
| // Same operation through flat Maps: upper-bound reference for BM_ReshapedExprFlattenRead. | |
| template <typename Scalar> | |
| static void BM_MapExprFlattenRead(benchmark::State& state) { | |
| const Index n = state.range(0); | |
| using Mat = Matrix<Scalar, Dynamic, Dynamic>; | |
| using Vec = Matrix<Scalar, Dynamic, 1>; | |
| Mat a = Mat::Random(n, n), b = Mat::Random(n, n); | |
| Vec dst(n * n); | |
| for (auto _ : state) { | |
| dst = Map<const Vec>(a.data(), a.size()) + Map<const Vec>(b.data(), b.size()); | |
| benchmark::DoNotOptimize(dst.data()); | |
| } | |
| state.SetBytesProcessed(state.iterations() * n * n * sizeof(Scalar)); | |
| } | |
| // Sum-reduce a flattened expression: like BM_ReshapedSum this isolates Eigen's packet path, | |
| // but through the non-direct evaluator, which previously also paid a div/mod index remap per | |
| // coefficient. | |
| template <typename Scalar> | |
| static void BM_ReshapedExprSum(benchmark::State& state) { | |
| const Index n = state.range(0); | |
| using Mat = Matrix<Scalar, Dynamic, Dynamic>; | |
| Mat a = Mat::Random(n, n), b = Mat::Random(n, n); | |
| Scalar acc(0); | |
| for (auto _ : state) { | |
| acc += (a + b).reshaped().sum(); | |
| benchmark::DoNotOptimize(acc); | |
| } | |
| // Like BM_ReshapedSum, but the summands a+b lie in [-2, 2], so double the tolerance. | |
| const Scalar sum = (a + b).reshaped().sum(); | |
| const Scalar ref = (a + b).sum(); | |
| if (numext::abs(sum - ref) > Scalar(32 * n * n) * NumTraits<Scalar>::epsilon()) { | |
| state.SkipWithError("reshaped expression sum differs from reference"); | |
| } | |
| state.SetBytesProcessed(state.iterations() * n * n * sizeof(Scalar)); | |
| } | |
| // Same reduction without the reshape: upper-bound reference for BM_ReshapedExprSum. | |
| template <typename Scalar> | |
| static void BM_ExprSum(benchmark::State& state) { | |
| const Index n = state.range(0); | |
| using Mat = Matrix<Scalar, Dynamic, Dynamic>; | |
| Mat a = Mat::Random(n, n), b = Mat::Random(n, n); | |
| Scalar acc(0); | |
| for (auto _ : state) { | |
| acc += (a + b).sum(); | |
| benchmark::DoNotOptimize(acc); | |
| } | |
| state.SetBytesProcessed(state.iterations() * n * n * sizeof(Scalar)); | |
| } | |
| // (matrix dimension n; the reshaped view has n*n elements) | |
| // clang-format off | |
| #define RESHAPED_SIZES ->RangeMultiplier(4)->Range(16, 1024) | |
| BENCHMARK(BM_ReshapedFlattenRead<float>) RESHAPED_SIZES ->Name("ReshapedFlattenRead_float"); | |
| BENCHMARK(BM_ReshapedFlattenRead<double>) RESHAPED_SIZES ->Name("ReshapedFlattenRead_double"); | |
| BENCHMARK(BM_MapFlattenRead<float>) RESHAPED_SIZES ->Name("MapFlattenRead_float"); | |
| BENCHMARK(BM_MapFlattenRead<double>) RESHAPED_SIZES ->Name("MapFlattenRead_double"); | |
| BENCHMARK(BM_ReshapedFlattenAccumulate<float>) RESHAPED_SIZES ->Name("ReshapedFlattenAccumulate_float"); | |
| BENCHMARK(BM_ReshapedFlattenAccumulate<double>) RESHAPED_SIZES ->Name("ReshapedFlattenAccumulate_double"); | |
| BENCHMARK(BM_MapFlattenAccumulate<float>) RESHAPED_SIZES ->Name("MapFlattenAccumulate_float"); | |
| BENCHMARK(BM_MapFlattenAccumulate<double>) RESHAPED_SIZES ->Name("MapFlattenAccumulate_double"); | |
| BENCHMARK(BM_ReshapedWrite<float>) RESHAPED_SIZES ->Name("ReshapedWrite_float"); | |
| BENCHMARK(BM_ReshapedWrite<double>) RESHAPED_SIZES ->Name("ReshapedWrite_double"); | |
| BENCHMARK(BM_ReshapedRowMajorFlatten<float>) RESHAPED_SIZES ->Name("ReshapedRowMajorFlatten_float"); | |
| BENCHMARK(BM_ReshapedRowMajorFlatten<double>) RESHAPED_SIZES ->Name("ReshapedRowMajorFlatten_double"); | |
| BENCHMARK(BM_ReshapedSum<float>) RESHAPED_SIZES ->Name("ReshapedSum_float"); | |
| BENCHMARK(BM_ReshapedSum<double>) RESHAPED_SIZES ->Name("ReshapedSum_double"); | |
| BENCHMARK(BM_MapSum<float>) RESHAPED_SIZES ->Name("MapSum_float"); | |
| BENCHMARK(BM_MapSum<double>) RESHAPED_SIZES ->Name("MapSum_double"); | |
| BENCHMARK(BM_ReshapedExprFlattenRead<float>) RESHAPED_SIZES ->Name("ReshapedExprFlattenRead_float"); | |
| BENCHMARK(BM_ReshapedExprFlattenRead<double>) RESHAPED_SIZES ->Name("ReshapedExprFlattenRead_double"); | |
| BENCHMARK(BM_MapExprFlattenRead<float>) RESHAPED_SIZES ->Name("MapExprFlattenRead_float"); | |
| BENCHMARK(BM_MapExprFlattenRead<double>) RESHAPED_SIZES ->Name("MapExprFlattenRead_double"); | |
| BENCHMARK(BM_ReshapedExprSum<float>) RESHAPED_SIZES ->Name("ReshapedExprSum_float"); | |
| BENCHMARK(BM_ReshapedExprSum<double>) RESHAPED_SIZES ->Name("ReshapedExprSum_double"); | |
| BENCHMARK(BM_ExprSum<float>) RESHAPED_SIZES ->Name("ExprSum_float"); | |
| BENCHMARK(BM_ExprSum<double>) RESHAPED_SIZES ->Name("ExprSum_double"); | |
| BENCHMARK(BM_ReshapedCwiseExp<float>) RESHAPED_SIZES ->Name("ReshapedCwiseExp_float"); | |
| BENCHMARK(BM_ReshapedCwiseExp<double>) RESHAPED_SIZES ->Name("ReshapedCwiseExp_double"); | |
| BENCHMARK(BM_MapCwiseExp<float>) RESHAPED_SIZES ->Name("MapCwiseExp_float"); | |
| BENCHMARK(BM_MapCwiseExp<double>) RESHAPED_SIZES ->Name("MapCwiseExp_double"); | |
| #undef RESHAPED_SIZES | |
| // clang-format on |