| // Benchmarks for Eigen TensorLayoutSwap. | |
| // 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; | |
| static void BM_LayoutSwap_2D(benchmark::State& state) { | |
| const int M = state.range(0); | |
| const int N = state.range(1); | |
| Tensor<Scalar, 2, ColMajor> A(M, N); | |
| A.setRandom(); | |
| for (auto _ : state) { | |
| Tensor<Scalar, 2, RowMajor> B = A.swap_layout(); | |
| benchmark::DoNotOptimize(B.data()); | |
| benchmark::ClobberMemory(); | |
| } | |
| // 1 read (A) + 1 write (B). | |
| state.SetBytesProcessed(state.iterations() * 2ll * static_cast<int64_t>(M) * N * sizeof(Scalar)); | |
| } | |
| static void BM_LayoutSwap_3D(benchmark::State& state) { | |
| const int D0 = state.range(0); | |
| const int D1 = state.range(1); | |
| const int D2 = state.range(2); | |
| Tensor<Scalar, 3, ColMajor> A(D0, D1, D2); | |
| A.setRandom(); | |
| for (auto _ : state) { | |
| Tensor<Scalar, 3, RowMajor> B = A.swap_layout(); | |
| benchmark::DoNotOptimize(B.data()); | |
| benchmark::ClobberMemory(); | |
| } | |
| // 1 read (A) + 1 write (B). | |
| state.SetBytesProcessed(state.iterations() * 2ll * static_cast<int64_t>(D0) * D1 * D2 * sizeof(Scalar)); | |
| } | |
| // Composing swap_layout with a coefficient-wise op forces evaluation through | |
| // the executor and exercises any subsequent block consumers. | |
| static void BM_LayoutSwap_Composed(benchmark::State& state) { | |
| const int M = state.range(0); | |
| const int N = state.range(1); | |
| Tensor<Scalar, 2, ColMajor> A(M, N); | |
| Tensor<Scalar, 2, ColMajor> B(M, N); | |
| A.setRandom(); | |
| B.setRandom(); | |
| for (auto _ : state) { | |
| Tensor<Scalar, 2, RowMajor> C = (A + B).swap_layout(); | |
| benchmark::DoNotOptimize(C.data()); | |
| benchmark::ClobberMemory(); | |
| } | |
| // 2 reads (A, B) + 1 write (C). | |
| state.SetBytesProcessed(state.iterations() * 3ll * static_cast<int64_t>(M) * N * sizeof(Scalar)); | |
| } | |
| // A transpose shuffle composed with swap_layout converts a matrix to the | |
| // opposite storage order while keeping its logical indexing: B(i,j) == A(i,j). | |
| // The shuffle serves materialized blocks that the swap re-wraps. | |
| static void BM_LayoutSwap_Shuffle2D(benchmark::State& state) { | |
| const Index M = state.range(0); | |
| const Index N = state.range(1); | |
| Tensor<Scalar, 2, ColMajor> A(M, N); | |
| A.setRandom(); | |
| Eigen::array<Index, 2> transpose = {1, 0}; | |
| Tensor<Scalar, 2, RowMajor> B; | |
| for (auto _ : state) { | |
| B = A.shuffle(transpose).swap_layout(); | |
| benchmark::DoNotOptimize(B.data()); | |
| benchmark::ClobberMemory(); | |
| } | |
| for (Index i = 0; i < M; ++i) { | |
| for (Index j = 0; j < N; ++j) { | |
| if (B(i, j) != A(i, j)) { | |
| state.SkipWithError("validation failed"); | |
| return; | |
| } | |
| } | |
| } | |
| // 1 read (A) + 1 write (B). | |
| state.SetBytesProcessed(state.iterations() * 2ll * static_cast<int64_t>(M) * N * sizeof(Scalar)); | |
| } | |
| static void BM_LayoutSwap_Shuffle3D(benchmark::State& state) { | |
| const Index D0 = state.range(0); | |
| const Index D1 = state.range(1); | |
| const Index D2 = state.range(2); | |
| Tensor<Scalar, 3, ColMajor> A(D0, D1, D2); | |
| A.setRandom(); | |
| Eigen::array<Index, 3> perm = {2, 0, 1}; | |
| const Tensor<Scalar, 3, ColMajor> ref = A.shuffle(perm); | |
| Tensor<Scalar, 3, RowMajor> B; | |
| for (auto _ : state) { | |
| B = A.shuffle(perm).swap_layout(); | |
| benchmark::DoNotOptimize(B.data()); | |
| benchmark::ClobberMemory(); | |
| } | |
| for (Index i = 0; i < ref.dimension(0); ++i) { | |
| for (Index j = 0; j < ref.dimension(1); ++j) { | |
| for (Index k = 0; k < ref.dimension(2); ++k) { | |
| if (B(k, j, i) != ref(i, j, k)) { | |
| state.SkipWithError("validation failed"); | |
| return; | |
| } | |
| } | |
| } | |
| } | |
| // 1 read (A) + 1 write (B). | |
| state.SetBytesProcessed(state.iterations() * 2ll * static_cast<int64_t>(D0) * D1 * D2 * sizeof(Scalar)); | |
| } | |
| // A cwise op on top of the shuffle serves lazy blocks that the layout swap | |
| // materializes itself. | |
| static void BM_LayoutSwap_ShuffleCwise2D(benchmark::State& state) { | |
| const Index M = state.range(0); | |
| const Index N = state.range(1); | |
| Tensor<Scalar, 2, ColMajor> A(M, N); | |
| Tensor<Scalar, 2, ColMajor> bias(N, M); | |
| A.setRandom(); | |
| bias.setRandom(); | |
| Eigen::array<Index, 2> transpose = {1, 0}; | |
| Tensor<Scalar, 2, RowMajor> B; | |
| for (auto _ : state) { | |
| B = (A.shuffle(transpose) + bias).swap_layout(); | |
| benchmark::DoNotOptimize(B.data()); | |
| benchmark::ClobberMemory(); | |
| } | |
| for (Index i = 0; i < M; ++i) { | |
| for (Index j = 0; j < N; ++j) { | |
| if (B(i, j) != A(i, j) + bias(j, i)) { | |
| state.SkipWithError("validation failed"); | |
| return; | |
| } | |
| } | |
| } | |
| // 2 reads (A, bias) + 1 write (B). | |
| state.SetBytesProcessed(state.iterations() * 3ll * static_cast<int64_t>(M) * N * sizeof(Scalar)); | |
| } | |
| // Write through a layout-swapped destination from a block-preferring source. | |
| static void BM_LayoutSwap_Write2D(benchmark::State& state) { | |
| const Index M = state.range(0); | |
| const Index N = state.range(1); | |
| Tensor<Scalar, 2, ColMajor> A(M, N); | |
| Tensor<Scalar, 2, ColMajor> bias(N, M); | |
| A.setRandom(); | |
| bias.setRandom(); | |
| Eigen::array<Index, 2> transpose = {1, 0}; | |
| Tensor<Scalar, 2, RowMajor> C(M, N); | |
| C.setZero(); | |
| for (auto _ : state) { | |
| C.swap_layout() = A.shuffle(transpose) + bias; | |
| benchmark::DoNotOptimize(C.data()); | |
| benchmark::ClobberMemory(); | |
| } | |
| for (Index i = 0; i < M; ++i) { | |
| for (Index j = 0; j < N; ++j) { | |
| if (C(i, j) != A(i, j) + bias(j, i)) { | |
| state.SkipWithError("validation failed"); | |
| return; | |
| } | |
| } | |
| } | |
| // 2 reads (A, bias) + 1 write (C). | |
| state.SetBytesProcessed(state.iterations() * 3ll * static_cast<int64_t>(M) * N * sizeof(Scalar)); | |
| } | |
| // {n, n} and {n, n, n}: explicit because dims are repeated. | |
| #define LAYOUT_SWAP_SIZES ->Args({64, 64})->Args({256, 256})->Args({1024, 1024}) | |
| #define LAYOUT_SWAP_3D_SIZES ->Args({32, 32, 32})->Args({64, 64, 64})->Args({128, 128, 128}) | |
| BENCHMARK(BM_LayoutSwap_2D) LAYOUT_SWAP_SIZES; | |
| BENCHMARK(BM_LayoutSwap_3D) LAYOUT_SWAP_3D_SIZES; | |
| BENCHMARK(BM_LayoutSwap_Composed) LAYOUT_SWAP_SIZES; | |
| BENCHMARK(BM_LayoutSwap_Shuffle2D) LAYOUT_SWAP_SIZES; | |
| BENCHMARK(BM_LayoutSwap_Shuffle3D) LAYOUT_SWAP_3D_SIZES; | |
| BENCHMARK(BM_LayoutSwap_ShuffleCwise2D) LAYOUT_SWAP_SIZES; | |
| BENCHMARK(BM_LayoutSwap_Write2D) LAYOUT_SWAP_SIZES; |