| // SPDX-FileCopyrightText: The Eigen Authors | |
| // SPDX-License-Identifier: MPL-2.0 | |
| #include <benchmark/benchmark.h> | |
| #include <contrib/Eigen/Tensor> | |
| using Eigen::Index; | |
| enum BlockViewComposition { | |
| ShuffleAdd, | |
| ShuffleAffine, | |
| ShuffleSelect, | |
| SwapAffine, | |
| CropBroadcast, | |
| BroadcastAffine, | |
| BroadcastBelow | |
| }; | |
| static const char* composition_name(int composition) { | |
| static const char* names[] = {"shuffle_add", "shuffle_affine", "shuffle_select", "swap_affine", | |
| "crop_broadcast", "broadcast_affine", "broadcast_below"}; | |
| return names[composition]; | |
| } | |
| template <typename Scalar, int Layout, typename Measure> | |
| static void block_view_composition(Index rows, Index cols, int composition, const Measure& measure) { | |
| using Tensor = Eigen::Tensor<Scalar, 2, Layout>; | |
| const bool shuffled = composition <= SwapAffine; | |
| Tensor input(shuffled ? cols : rows, shuffled ? rows : cols), b(rows, cols), c(rows, cols), d(rows, cols); | |
| Tensor tile(rows / 2, cols / 2), tile_b(rows / 2, cols / 2), tile_c(rows / 2, cols / 2); | |
| Eigen::Tensor<bool, 2, Layout> mask(rows, cols); | |
| const auto fill = [](auto& tensor, Index multiplier) { | |
| for (Index j = 0; j < tensor.dimension(1); ++j) | |
| for (Index i = 0; i < tensor.dimension(0); ++i) tensor(i, j) = Scalar((i * multiplier + j * 7) % 23) - Scalar(11); | |
| }; | |
| fill(input, 3); | |
| fill(b, 5); | |
| fill(c, 7); | |
| fill(d, 11); | |
| fill(tile, 3); | |
| fill(tile_b, 5); | |
| fill(tile_c, 7); | |
| for (Index j = 0; j < cols; ++j) | |
| for (Index i = 0; i < rows; ++i) mask(i, j) = (i + j) % 3 != 0; | |
| const Eigen::array<int, 2> transpose{{1, 0}}; | |
| const Eigen::array<Index, 2> repeats{{2, 2}}, offsets{{rows / 4, cols / 4}}, sizes{{rows / 2, cols / 2}}; | |
| Tensor output(rows, cols); | |
| switch (composition) { | |
| case ShuffleAdd: | |
| measure(output, input.shuffle(transpose) + b, [&](Index i, Index j) { return input(j, i) + b(i, j); }); | |
| break; | |
| case ShuffleAffine: | |
| measure(output, ((input.shuffle(transpose) + b) * c + d).cwiseMax(Scalar(0)), [&](Index i, Index j) { | |
| return Eigen::numext::maxi(Scalar(0), (input(j, i) + b(i, j)) * c(i, j) + d(i, j)); | |
| }); | |
| break; | |
| case ShuffleSelect: | |
| measure(output, mask.select(input.shuffle(transpose) + b * c, d), | |
| [&](Index i, Index j) { return mask(i, j) ? input(j, i) + b(i, j) * c(i, j) : d(i, j); }); | |
| break; | |
| case SwapAffine: { | |
| Eigen::Tensor<Scalar, 2, Layout == Eigen::ColMajor ? Eigen::RowMajor : Eigen::ColMajor> swapped(cols, rows); | |
| measure(swapped, (input.shuffle(transpose) + b * c).swap_layout(), | |
| [&](Index j, Index i) { return input(j, i) + b(i, j) * c(i, j); }); | |
| break; | |
| } | |
| case CropBroadcast: | |
| measure(output, (input + b * c).slice(offsets, sizes).broadcast(repeats), [&](Index i, Index j) { | |
| const Index row = offsets[0] + i % sizes[0], col = offsets[1] + j % sizes[1]; | |
| return input(row, col) + b(row, col) * c(row, col); | |
| }); | |
| break; | |
| case BroadcastAffine: | |
| measure(output, tile.broadcast(repeats) + b * c, | |
| [&](Index i, Index j) { return tile(i % sizes[0], j % sizes[1]) + b(i, j) * c(i, j); }); | |
| break; | |
| case BroadcastBelow: | |
| measure(output, (tile + tile_b * tile_c).broadcast(repeats), [&](Index i, Index j) { | |
| const Index row = i % sizes[0], col = j % sizes[1]; | |
| return tile(row, col) + tile_b(row, col) * tile_c(row, col); | |
| }); | |
| break; | |
| } | |
| } | |
| template <typename Scalar, int Layout> | |
| static void BM_BlockViewCompositions(benchmark::State& state) { | |
| const Index rows = state.range(0), cols = state.range(1); | |
| const int composition = static_cast<int>(state.range(2)); | |
| const bool public_assignment = state.range(3) != 0; | |
| const auto measure = [&](auto& output, const auto& expression, const auto& expected) { | |
| using Output = Eigen::internal::remove_all_t<decltype(output)>; | |
| using Expression = Eigen::internal::remove_all_t<decltype(expression)>; | |
| using Assign = Eigen::TensorAssignOp<Output, const Expression>; | |
| using Device = Eigen::DefaultDevice; | |
| static_assert(Eigen::internal::IsTileable<Device, const Assign>::value == Eigen::internal::TiledEvaluation::On, | |
| "Composite assignments must select block evaluation"); | |
| constexpr bool Vectorizable = Eigen::internal::IsVectorizable<Device, const Assign>::value; | |
| using Untiled = | |
| Eigen::internal::TensorExecutor<const Assign, Device, Vectorizable, Eigen::internal::TiledEvaluation::Off>; | |
| const Assign assign(output, expression); | |
| const auto evaluate = [&]() { | |
| if (public_assignment) | |
| output = expression; | |
| else | |
| Untiled::run(assign); | |
| }; | |
| evaluate(); | |
| for (Index j = 0; j < output.dimension(1); ++j) { | |
| for (Index i = 0; i < output.dimension(0); ++i) { | |
| if (output(i, j) != expected(i, j)) { | |
| state.SkipWithError("incorrect composite result"); | |
| return; | |
| } | |
| } | |
| } | |
| for (auto unused : state) { | |
| evaluate(); | |
| benchmark::DoNotOptimize(output.data()); | |
| benchmark::ClobberMemory(); | |
| } | |
| }; | |
| block_view_composition<Scalar, Layout>(rows, cols, composition, measure); | |
| state.SetLabel(composition_name(composition)); | |
| state.SetItemsProcessed(state.iterations() * rows * cols); | |
| } | |
| #define COMPOSITION_SHAPE(Rows, Cols) ->ArgsProduct({{Rows}, {Cols}, {0, 1, 2, 3, 4, 5, 6}, {0, 1}}) | |
| #define REGISTER_COMPOSITIONS(Scalar, Layout) \ | |
| BENCHMARK_TEMPLATE(BM_BlockViewCompositions, Scalar, Layout) \ | |
| COMPOSITION_SHAPE(64, 64) COMPOSITION_SHAPE(256, 256) COMPOSITION_SHAPE(258, 194) COMPOSITION_SHAPE(1024, 1024) | |
| REGISTER_COMPOSITIONS(float, Eigen::ColMajor); | |
| REGISTER_COMPOSITIONS(float, Eigen::RowMajor); | |
| REGISTER_COMPOSITIONS(double, Eigen::ColMajor); | |
| REGISTER_COMPOSITIONS(double, Eigen::RowMajor); | |
| #undef REGISTER_COMPOSITIONS | |
| #undef COMPOSITION_SHAPE |