blob: f5b32b6fa814dfacee6cc56b7d7af4c23bfb8b73 [file]
// Benchmarks for Eigen TensorReverse.
// 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;
// --- Reverse only the inner-most (contiguous) dimension. SIMD preverse case. ---
static void BM_Reverse_Inner(benchmark::State& state) {
const int M = state.range(0);
const int N = state.range(1);
Tensor<Scalar, 2> A(M, N);
A.setRandom();
array<bool, 2> dim_rev = {true, false};
for (auto _ : state) {
Tensor<Scalar, 2> B = A.reverse(dim_rev);
benchmark::DoNotOptimize(B.data());
benchmark::ClobberMemory();
}
state.SetBytesProcessed(state.iterations() * static_cast<int64_t>(M) * N * sizeof(Scalar));
}
// --- Reverse only an outer dimension. Inner dim stays contiguous. ---
static void BM_Reverse_Outer(benchmark::State& state) {
const int M = state.range(0);
const int N = state.range(1);
Tensor<Scalar, 2> A(M, N);
A.setRandom();
array<bool, 2> dim_rev = {false, true};
for (auto _ : state) {
Tensor<Scalar, 2> B = A.reverse(dim_rev);
benchmark::DoNotOptimize(B.data());
benchmark::ClobberMemory();
}
state.SetBytesProcessed(state.iterations() * static_cast<int64_t>(M) * N * sizeof(Scalar));
}
// --- Reverse every dimension. ---
static void BM_Reverse_All(benchmark::State& state) {
const int M = state.range(0);
const int N = state.range(1);
Tensor<Scalar, 2> A(M, N);
A.setRandom();
array<bool, 2> dim_rev = {true, true};
for (auto _ : state) {
Tensor<Scalar, 2> B = A.reverse(dim_rev);
benchmark::DoNotOptimize(B.data());
benchmark::ClobberMemory();
}
state.SetBytesProcessed(state.iterations() * static_cast<int64_t>(M) * N * sizeof(Scalar));
}
// --- 3D reverse with the inner dim reversed (typical CNN-style layout). ---
static void BM_Reverse_3D_Inner(benchmark::State& state) {
const int D0 = state.range(0);
const int D1 = state.range(1);
const int D2 = state.range(2);
Tensor<Scalar, 3> A(D0, D1, D2);
A.setRandom();
array<bool, 3> dim_rev = {true, false, false};
for (auto _ : state) {
Tensor<Scalar, 3> B = A.reverse(dim_rev);
benchmark::DoNotOptimize(B.data());
benchmark::ClobberMemory();
}
state.SetBytesProcessed(state.iterations() * static_cast<int64_t>(D0) * D1 * D2 * sizeof(Scalar));
}
// Sweep sizes that span L1 (~32 KB), L2 (~256 KB), and LLC (~MBs) for float
// tensors. Bytes per element = 4, so per-side sizes:
// 64x64 = 16 KB (L1)
// 256x256 = 256 KB (L2)
// 1024x1024 = 4 MB (LLC / DRAM)
// --- Writes through a reversed destination (lvalue writePacket path) ---
static void BM_ReverseWrite_Inner(benchmark::State& state) {
const int M = state.range(0);
const int N = state.range(1);
Tensor<Scalar, 2> src(M, N);
src.setRandom();
Tensor<Scalar, 2> dst(M, N);
dst.setZero();
array<bool, 2> dim_rev = {true, false};
for (auto _ : state) {
dst.reverse(dim_rev) = src;
benchmark::DoNotOptimize(dst.data());
benchmark::ClobberMemory();
}
// Validate outside the timed loop.
for (int i = 0; i < M; ++i) {
for (int j = 0; j < N; ++j) {
if (dst(M - 1 - i, j) != src(i, j)) {
state.SkipWithError("validation failed");
return;
}
}
}
state.SetBytesProcessed(state.iterations() * static_cast<int64_t>(M) * N * sizeof(Scalar));
}
static void BM_ReverseWrite_Outer(benchmark::State& state) {
const int M = state.range(0);
const int N = state.range(1);
Tensor<Scalar, 2> src(M, N);
src.setRandom();
Tensor<Scalar, 2> dst(M, N);
dst.setZero();
array<bool, 2> dim_rev = {false, true};
for (auto _ : state) {
dst.reverse(dim_rev) = src;
benchmark::DoNotOptimize(dst.data());
benchmark::ClobberMemory();
}
for (int i = 0; i < M; ++i) {
for (int j = 0; j < N; ++j) {
if (dst(i, N - 1 - j) != src(i, j)) {
state.SkipWithError("validation failed");
return;
}
}
}
state.SetBytesProcessed(state.iterations() * static_cast<int64_t>(M) * N * sizeof(Scalar));
}
// --- Writes into a reversed destination from a block-preferring source. The
// shuffle routes the assignment through the tiled executor, so the reverse
// destination serves it via writeBlock instead of per-packet index math. ---
static void BM_ReverseWrite_FromShuffle_Inner(benchmark::State& state) {
const int M = state.range(0);
const int N = state.range(1);
Tensor<Scalar, 2> src(N, M);
src.setRandom();
Tensor<Scalar, 2> dst(M, N);
dst.setZero();
Eigen::array<Index, 2> transpose = {1, 0};
array<bool, 2> dim_rev = {true, false};
for (auto _ : state) {
dst.reverse(dim_rev) = src.shuffle(transpose);
benchmark::DoNotOptimize(dst.data());
benchmark::ClobberMemory();
}
for (int i = 0; i < M; ++i) {
for (int j = 0; j < N; ++j) {
if (dst(M - 1 - i, j) != src(j, i)) {
state.SkipWithError("validation failed");
return;
}
}
}
state.SetBytesProcessed(state.iterations() * static_cast<int64_t>(M) * N * sizeof(Scalar));
}
static void BM_ReverseWrite_FromShuffle_Outer(benchmark::State& state) {
const int M = state.range(0);
const int N = state.range(1);
Tensor<Scalar, 2> src(N, M);
src.setRandom();
Tensor<Scalar, 2> dst(M, N);
dst.setZero();
Eigen::array<Index, 2> transpose = {1, 0};
array<bool, 2> dim_rev = {false, true};
for (auto _ : state) {
dst.reverse(dim_rev) = src.shuffle(transpose);
benchmark::DoNotOptimize(dst.data());
benchmark::ClobberMemory();
}
for (int i = 0; i < M; ++i) {
for (int j = 0; j < N; ++j) {
if (dst(i, N - 1 - j) != src(j, i)) {
state.SkipWithError("validation failed");
return;
}
}
}
state.SetBytesProcessed(state.iterations() * static_cast<int64_t>(M) * N * sizeof(Scalar));
}
// A broadcast right-hand side is the motivating case for the reverse
// destination's writeBlock: broadcast's packet path pays heavy per-packet
// index math, while its block path is a bulk copy.
static void BM_ReverseWrite_FromBroadcast(benchmark::State& state) {
const int M = state.range(0);
const int N = state.range(1);
Tensor<Scalar, 2> src(M / 4, N);
src.setRandom();
Tensor<Scalar, 2> dst(M, N);
dst.setZero();
Eigen::array<Index, 2> bcast = {4, 1};
array<bool, 2> dim_rev = {true, false};
for (auto _ : state) {
dst.reverse(dim_rev) = src.broadcast(bcast);
benchmark::DoNotOptimize(dst.data());
benchmark::ClobberMemory();
}
for (int i = 0; i < M; ++i) {
for (int j = 0; j < N; ++j) {
if (dst(M - 1 - i, j) != src(i % (M / 4), j)) {
state.SkipWithError("validation failed");
return;
}
}
}
state.SetBytesProcessed(state.iterations() * static_cast<int64_t>(M) * N * sizeof(Scalar));
}
// Lazy right-hand-side blocks: writeBlock evaluates the re-reversed cwise
// expression straight into the destination box.
static void BM_ReverseWrite_FromShuffleExpr(benchmark::State& state) {
const int M = state.range(0);
const int N = state.range(1);
Tensor<Scalar, 2> src(N, M);
src.setRandom();
Tensor<Scalar, 2> dst(M, N);
dst.setZero();
Eigen::array<Index, 2> transpose = {1, 0};
array<bool, 2> dim_rev = {true, false};
for (auto _ : state) {
dst.reverse(dim_rev) = src.shuffle(transpose) + src.shuffle(transpose).constant(1.0f);
benchmark::DoNotOptimize(dst.data());
benchmark::ClobberMemory();
}
for (int i = 0; i < M; ++i) {
for (int j = 0; j < N; ++j) {
if (dst(M - 1 - i, j) != src(j, i) + 1.0f) {
state.SkipWithError("validation failed");
return;
}
}
}
state.SetBytesProcessed(state.iterations() * static_cast<int64_t>(M) * N * sizeof(Scalar));
}
// clang-format off
#define REVERSE_SIZES \
->Args({64, 64})->Args({256, 256})->Args({1024, 1024})
// 128 KB / 1 MB / 8 MB
#define REVERSE_3D_SIZES \
->Args({32, 32, 32})->Args({64, 64, 64})->Args({128, 128, 128})
// clang-format on
BENCHMARK(BM_Reverse_Inner) REVERSE_SIZES;
BENCHMARK(BM_Reverse_Outer) REVERSE_SIZES;
BENCHMARK(BM_Reverse_All) REVERSE_SIZES;
BENCHMARK(BM_Reverse_3D_Inner) REVERSE_3D_SIZES;
BENCHMARK(BM_ReverseWrite_Inner) REVERSE_SIZES;
BENCHMARK(BM_ReverseWrite_Outer) REVERSE_SIZES;
BENCHMARK(BM_ReverseWrite_FromShuffle_Inner) REVERSE_SIZES;
BENCHMARK(BM_ReverseWrite_FromShuffle_Outer) REVERSE_SIZES;
BENCHMARK(BM_ReverseWrite_FromBroadcast) REVERSE_SIZES;
BENCHMARK(BM_ReverseWrite_FromShuffleExpr) REVERSE_SIZES;