| // Benchmarks for compositions over Eigen Tensor custom ops. |
| // SPDX-FileCopyrightText: The Eigen Authors |
| // SPDX-License-Identifier: MPL-2.0 |
| |
| #include <benchmark/benchmark.h> |
| #include <contrib/Eigen/Tensor> |
| |
| using namespace Eigen; |
| |
| typedef float Scalar; |
| |
| // A trivial custom op (scale by 2) with the same output dimensions as its |
| // input. The interesting part is not the op itself but how its materialized |
| // result composes with downstream expressions. |
| struct ScaleByTwo { |
| DSizes<DenseIndex, 2> dimensions(const Tensor<Scalar, 2>& input) const { return input.dimensions(); } |
| |
| template <typename Output, typename Device> |
| void eval(const Tensor<Scalar, 2>& input, Output& output, const Device& device) const { |
| output.device(device) = input * Scalar(2); |
| } |
| }; |
| |
| // Custom op consumed by a transpose. With block access on the custom-op |
| // evaluator the shuffle runs as a cache-blocked TensorBlockIO transpose over |
| // the custom op's raw buffer; without it, as a per-element gather. |
| static void BM_CustomOpShuffle(benchmark::State& state) { |
| const int M = state.range(0); |
| const int N = state.range(1); |
| |
| Tensor<Scalar, 2> mat(M, N); |
| Tensor<Scalar, 2> result(N, M); |
| mat.setRandom(); |
| |
| Eigen::array<int, 2> transpose = {1, 0}; |
| |
| for (auto _ : state) { |
| result = mat.customOp(ScaleByTwo()).shuffle(transpose); |
| benchmark::DoNotOptimize(result.data()); |
| benchmark::ClobberMemory(); |
| } |
| |
| // Validate outside the timed loop. |
| for (int i = 0; i < N; ++i) { |
| for (int j = 0; j < M; ++j) { |
| if (result(i, j) != Scalar(2) * mat(j, i)) { |
| state.SkipWithError("validation failed"); |
| return; |
| } |
| } |
| } |
| state.SetBytesProcessed(state.iterations() * M * N * sizeof(Scalar)); |
| } |
| |
| // Custom op consumed by a contiguous slice. With raw access on the custom-op |
| // evaluator the slice hits its memcpy fast path. |
| static void BM_CustomOpSlice(benchmark::State& state) { |
| const int M = state.range(0); |
| const int N = state.range(1); |
| |
| Tensor<Scalar, 2> mat(M, N); |
| mat.setRandom(); |
| |
| const Eigen::array<Index, 2> offsets = {0, N / 4}; |
| const Eigen::array<Index, 2> extents = {M, N / 2}; |
| |
| Tensor<Scalar, 2> result; |
| for (auto _ : state) { |
| result = mat.customOp(ScaleByTwo()).slice(offsets, extents); |
| benchmark::DoNotOptimize(result.data()); |
| benchmark::ClobberMemory(); |
| } |
| |
| for (Index i = 0; i < extents[0]; ++i) { |
| for (Index j = 0; j < extents[1]; ++j) { |
| if (result(i, j) != Scalar(2) * mat(i, N / 4 + j)) { |
| state.SkipWithError("validation failed"); |
| return; |
| } |
| } |
| } |
| state.SetBytesProcessed(state.iterations() * extents[0] * extents[1] * sizeof(Scalar)); |
| } |
| |
| #define CUSTOM_OP_SIZES ->ArgsProduct({{256, 1024, 4096}, {256, 1024}}) |
| |
| BENCHMARK(BM_CustomOpShuffle) CUSTOM_OP_SIZES; |
| BENCHMARK(BM_CustomOpSlice) CUSTOM_OP_SIZES; |