blob: 6b9efc0940f334ed04920cfccfd4cc62b8885ec5 [file]
// Benchmarks for Eigen Tensor striding (subsampling views).
// 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;
// --- Rank-2 read, outer dimension strided (inner runs stay contiguous) ---
static void BM_StrideReadOuter2D(benchmark::State& state) {
const Index M = state.range(0);
const Index N = state.range(1);
const Index S = state.range(2);
Tensor<Scalar, 2> A(M, N);
A.setRandom();
Eigen::array<Index, 2> strides = {1, S}; // ColMajor: dimension 0 is inner.
Tensor<Scalar, 2> B;
for (auto _ : state) {
B = A.stride(strides);
benchmark::DoNotOptimize(B.data());
benchmark::ClobberMemory();
}
for (Index i = 0; i < B.dimension(0); ++i) {
for (Index j = 0; j < B.dimension(1); ++j) {
if (B(i, j) != A(i, j * S)) {
state.SkipWithError("validation failed");
return;
}
}
}
state.SetItemsProcessed(state.iterations() * B.size());
}
// --- Rank-2 read, inner dimension strided (gather within inner runs) ---
static void BM_StrideReadInner2D(benchmark::State& state) {
const Index M = state.range(0);
const Index N = state.range(1);
const Index S = state.range(2);
Tensor<Scalar, 2> A(M, N);
A.setRandom();
Eigen::array<Index, 2> strides = {S, 1};
Tensor<Scalar, 2> B;
for (auto _ : state) {
B = A.stride(strides);
benchmark::DoNotOptimize(B.data());
benchmark::ClobberMemory();
}
for (Index i = 0; i < B.dimension(0); ++i) {
for (Index j = 0; j < B.dimension(1); ++j) {
if (B(i, j) != A(i * S, j)) {
state.SkipWithError("validation failed");
return;
}
}
}
state.SetItemsProcessed(state.iterations() * B.size());
}
// --- Identity striding (all strides 1, measures overhead) ---
static void BM_StrideIdentity2D(benchmark::State& state) {
const Index M = state.range(0);
const Index N = state.range(1);
Tensor<Scalar, 2> A(M, N);
A.setRandom();
Eigen::array<Index, 2> strides = {1, 1};
Tensor<Scalar, 2> B;
for (auto _ : state) {
B = A.stride(strides);
benchmark::DoNotOptimize(B.data());
benchmark::ClobberMemory();
}
for (Index i = 0; i < B.dimension(0); ++i) {
for (Index j = 0; j < B.dimension(1); ++j) {
if (B(i, j) != A(i, j)) {
state.SkipWithError("validation failed");
return;
}
}
}
state.SetItemsProcessed(state.iterations() * B.size());
}
// --- Rank-3 read, all dimensions strided ---
static void BM_StrideRead3D(benchmark::State& state) {
const Index D0 = state.range(0);
const Index D1 = state.range(1);
const Index D2 = state.range(2);
Tensor<Scalar, 3> A(D0, D1, D2);
A.setRandom();
Eigen::array<Index, 3> strides = {2, 2, 2};
Tensor<Scalar, 3> B;
for (auto _ : state) {
B = A.stride(strides);
benchmark::DoNotOptimize(B.data());
benchmark::ClobberMemory();
}
for (Index i = 0; i < B.dimension(0); ++i) {
for (Index j = 0; j < B.dimension(1); ++j) {
for (Index k = 0; k < B.dimension(2); ++k) {
if (B(i, j, k) != A(i * 2, j * 2, k * 2)) {
state.SkipWithError("validation failed");
return;
}
}
}
}
state.SetItemsProcessed(state.iterations() * B.size());
}
// --- Expression-sourced read (no raw buffer behind the striding) ---
static void BM_StrideExprInner2D(benchmark::State& state) {
const Index M = state.range(0);
const Index N = state.range(1);
const Index S = state.range(2);
Tensor<Scalar, 2> A(M, N);
A.setRandom();
Eigen::array<Index, 2> strides = {S, 1};
Tensor<Scalar, 2> B;
for (auto _ : state) {
B = (A + A.constant(Scalar(1))).stride(strides);
benchmark::DoNotOptimize(B.data());
benchmark::ClobberMemory();
}
for (Index i = 0; i < B.dimension(0); ++i) {
for (Index j = 0; j < B.dimension(1); ++j) {
if (B(i, j) != A(i * S, j) + Scalar(1)) {
state.SkipWithError("validation failed");
return;
}
}
}
state.SetItemsProcessed(state.iterations() * B.size());
}
// --- Rank-2 write through a strided destination ---
static void BM_StrideWriteOuter2D(benchmark::State& state) {
const Index M = state.range(0);
const Index N = state.range(1);
const Index S = state.range(2);
Tensor<Scalar, 2> A(M, (N + S - 1) / S);
A.setRandom();
Tensor<Scalar, 2> C(M, N);
C.setZero();
Eigen::array<Index, 2> strides = {1, S};
for (auto _ : state) {
C.stride(strides) = A;
benchmark::DoNotOptimize(C.data());
benchmark::ClobberMemory();
}
for (Index i = 0; i < A.dimension(0); ++i) {
for (Index j = 0; j < A.dimension(1); ++j) {
if (C(i, j * S) != A(i, j)) {
state.SkipWithError("validation failed");
return;
}
}
}
state.SetItemsProcessed(state.iterations() * A.size());
}
static void BM_StrideWriteInner2D(benchmark::State& state) {
const Index M = state.range(0);
const Index N = state.range(1);
const Index S = state.range(2);
Tensor<Scalar, 2> A((M + S - 1) / S, N);
A.setRandom();
Tensor<Scalar, 2> C(M, N);
C.setZero();
Eigen::array<Index, 2> strides = {S, 1};
for (auto _ : state) {
C.stride(strides) = A;
benchmark::DoNotOptimize(C.data());
benchmark::ClobberMemory();
}
for (Index i = 0; i < A.dimension(0); ++i) {
for (Index j = 0; j < A.dimension(1); ++j) {
if (C(i * S, j) != A(i, j)) {
state.SkipWithError("validation failed");
return;
}
}
}
state.SetItemsProcessed(state.iterations() * A.size());
}
// clang-format off
#define STRIDE_2D_SIZES ->ArgsProduct({{256, 1024}, {1024}, {2, 4}})
#define STRIDE_2D_PLAIN_SIZES ->Args({256, 1024})->Args({1024, 1024})
#define STRIDE_3D_SIZES ->Args({64, 64, 64})->Args({128, 128, 64})
// clang-format on
BENCHMARK(BM_StrideReadOuter2D) STRIDE_2D_SIZES;
BENCHMARK(BM_StrideReadInner2D) STRIDE_2D_SIZES;
BENCHMARK(BM_StrideIdentity2D) STRIDE_2D_PLAIN_SIZES;
BENCHMARK(BM_StrideRead3D) STRIDE_3D_SIZES;
BENCHMARK(BM_StrideExprInner2D) STRIDE_2D_SIZES;
BENCHMARK(BM_StrideWriteOuter2D) STRIDE_2D_SIZES;
BENCHMARK(BM_StrideWriteInner2D) STRIDE_2D_SIZES;