blob: 603cc134db176351958788a94bd4e5513ed8cd88 [file] [edit]
// Benchmarks for Eigen TensorRoll.
// 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;
// --- Roll only the inner-most (contiguous) dimension. ---
static void BM_Roll_Inner(benchmark::State& state) {
const int M = state.range(0);
const int N = state.range(1);
const int shift = state.range(2);
Tensor<Scalar, 2> A(M, N);
A.setRandom();
array<Index, 2> rolls = {shift, 0};
for (auto _ : state) {
Tensor<Scalar, 2> B = A.roll(rolls);
benchmark::DoNotOptimize(B.data());
benchmark::ClobberMemory();
}
state.SetBytesProcessed(state.iterations() * static_cast<int64_t>(M) * N * sizeof(Scalar));
}
// --- Roll only an outer dimension. Inner dim stays contiguous. ---
static void BM_Roll_Outer(benchmark::State& state) {
const int M = state.range(0);
const int N = state.range(1);
const int shift = state.range(2);
Tensor<Scalar, 2> A(M, N);
A.setRandom();
array<Index, 2> rolls = {0, shift};
for (auto _ : state) {
Tensor<Scalar, 2> B = A.roll(rolls);
benchmark::DoNotOptimize(B.data());
benchmark::ClobberMemory();
}
state.SetBytesProcessed(state.iterations() * static_cast<int64_t>(M) * N * sizeof(Scalar));
}
// --- Roll every dimension. ---
static void BM_Roll_All(benchmark::State& state) {
const int M = state.range(0);
const int N = state.range(1);
const int shift = state.range(2);
Tensor<Scalar, 2> A(M, N);
A.setRandom();
array<Index, 2> rolls = {shift, shift};
for (auto _ : state) {
Tensor<Scalar, 2> B = A.roll(rolls);
benchmark::DoNotOptimize(B.data());
benchmark::ClobberMemory();
}
state.SetBytesProcessed(state.iterations() * static_cast<int64_t>(M) * N * sizeof(Scalar));
}
// --- 3D roll with the inner dim shifted. ---
static void BM_Roll_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<Index, 3> rolls = {D0 / 4, 0, 0};
for (auto _ : state) {
Tensor<Scalar, 3> B = A.roll(rolls);
benchmark::DoNotOptimize(B.data());
benchmark::ClobberMemory();
}
state.SetBytesProcessed(state.iterations() * static_cast<int64_t>(D0) * D1 * D2 * sizeof(Scalar));
}
// --- Writes through a rolled destination (lvalue writeBlock path, which
// scatters each block into its wrap-around pieces) ---
static void BM_RollWrite_Inner(benchmark::State& state) {
const int M = state.range(0);
const int N = state.range(1);
const int shift = state.range(2);
Tensor<Scalar, 2> src(M, N);
src.setRandom();
Tensor<Scalar, 2> dst(M, N);
dst.setZero();
array<Index, 2> rolls = {shift, 0};
for (auto _ : state) {
dst.roll(rolls) = src;
benchmark::DoNotOptimize(dst.data());
benchmark::ClobberMemory();
}
for (int i = 0; i < M; ++i) {
for (int j = 0; j < N; ++j) {
if (dst((i + shift) % M, j) != src(i, j)) {
state.SkipWithError("validation failed");
return;
}
}
}
state.SetBytesProcessed(state.iterations() * static_cast<int64_t>(M) * N * sizeof(Scalar));
}
static void BM_RollWrite_Outer(benchmark::State& state) {
const int M = state.range(0);
const int N = state.range(1);
const int shift = state.range(2);
Tensor<Scalar, 2> src(M, N);
src.setRandom();
Tensor<Scalar, 2> dst(M, N);
dst.setZero();
array<Index, 2> rolls = {0, shift};
for (auto _ : state) {
dst.roll(rolls) = src;
benchmark::DoNotOptimize(dst.data());
benchmark::ClobberMemory();
}
for (int i = 0; i < M; ++i) {
for (int j = 0; j < N; ++j) {
if (dst(i, (j + shift) % N) != src(i, j)) {
state.SkipWithError("validation failed");
return;
}
}
}
state.SetBytesProcessed(state.iterations() * static_cast<int64_t>(M) * N * sizeof(Scalar));
}
// Lazy right-hand-side blocks: writeBlock materializes the cwise expression
// into a temporary before scattering the pieces.
static void BM_RollWrite_Expr(benchmark::State& state) {
const int M = state.range(0);
const int N = state.range(1);
const int shift = state.range(2);
Tensor<Scalar, 2> src(M, N);
src.setRandom();
Tensor<Scalar, 2> dst(M, N);
dst.setZero();
array<Index, 2> rolls = {shift, shift};
for (auto _ : state) {
dst.roll(rolls) = src + src.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((i + shift) % M, (j + shift) % N) != src(i, j) + 1.0f) {
state.SkipWithError("validation failed");
return;
}
}
}
state.SetBytesProcessed(state.iterations() * static_cast<int64_t>(M) * N * sizeof(Scalar));
}
// clang-format off
#define ROLL_SIZES \
->Args({64, 64, 1})->Args({64, 64, 13}) \
->Args({256, 256, 1})->Args({256, 256, 13}) \
->Args({1024, 1024, 1})->Args({1024, 1024, 13})
// Transition sizes around the tiled-evaluation crossover (~8x8) for the
// write benchmarks: below it the fixed tiled-executor setup cost dominates.
#define ROLL_WRITE_SIZES \
->Args({2, 2, 1})->Args({4, 4, 1})->Args({8, 8, 1})->Args({16, 16, 3})->Args({32, 32, 3}) \
->Args({64, 64, 1})->Args({64, 64, 13}) \
->Args({256, 256, 1})->Args({256, 256, 13}) \
->Args({1024, 1024, 1})->Args({1024, 1024, 13})
#define ROLL_3D_SIZES \
->Args({32, 32, 32})->Args({64, 64, 64})->Args({128, 128, 128})
// clang-format on
BENCHMARK(BM_Roll_Inner) ROLL_SIZES;
BENCHMARK(BM_Roll_Outer) ROLL_SIZES;
BENCHMARK(BM_Roll_All) ROLL_SIZES;
BENCHMARK(BM_Roll_3D_Inner) ROLL_3D_SIZES;
BENCHMARK(BM_RollWrite_Inner) ROLL_WRITE_SIZES;
BENCHMARK(BM_RollWrite_Outer) ROLL_WRITE_SIZES;
BENCHMARK(BM_RollWrite_Expr) ROLL_WRITE_SIZES;