Add benchmarks for unsupported modules and extend supported benchmarks

libeigen/eigen!2179

Closes #3036

Co-authored-by: Rasmus Munk Larsen <rmlarsen@gmail.com>
This commit is contained in:
Rasmus Munk Larsen
2026-02-24 17:12:33 -08:00
parent fa567f6bcd
commit 16da0279f1
33 changed files with 2320 additions and 10 deletions

View File

@@ -0,0 +1,8 @@
eigen_add_benchmark(bench_contraction bench_contraction.cpp)
eigen_add_benchmark(bench_convolution bench_convolution.cpp)
eigen_add_benchmark(bench_reduction bench_reduction.cpp)
eigen_add_benchmark(bench_broadcasting bench_broadcasting.cpp)
eigen_add_benchmark(bench_shuffling bench_shuffling.cpp)
eigen_add_benchmark(bench_tensor_fft bench_tensor_fft.cpp)
eigen_add_benchmark(bench_morphing bench_morphing.cpp)
eigen_add_benchmark(bench_coefficient_wise bench_coefficient_wise.cpp)

View File

@@ -0,0 +1,111 @@
// Benchmarks for Eigen Tensor broadcasting.
// Tests broadcasting along various dimensions and ranks.
#include <benchmark/benchmark.h>
#include <unsupported/Eigen/CXX11/Tensor>
using namespace Eigen;
typedef float Scalar;
// --- Broadcast row vector {1,N} -> {M,N} ---
static void BM_BroadcastRow(benchmark::State& state) {
const int M = state.range(0);
const int N = state.range(1);
Tensor<Scalar, 2> row(1, N);
Tensor<Scalar, 2> result(M, N);
row.setRandom();
Eigen::array<int, 2> bcast = {M, 1};
for (auto _ : state) {
result = row.broadcast(bcast);
benchmark::DoNotOptimize(result.data());
benchmark::ClobberMemory();
}
state.SetBytesProcessed(state.iterations() * M * N * sizeof(Scalar));
}
// --- Broadcast col vector {M,1} -> {M,N} ---
static void BM_BroadcastCol(benchmark::State& state) {
const int M = state.range(0);
const int N = state.range(1);
Tensor<Scalar, 2> col(M, 1);
Tensor<Scalar, 2> result(M, N);
col.setRandom();
Eigen::array<int, 2> bcast = {1, N};
for (auto _ : state) {
result = col.broadcast(bcast);
benchmark::DoNotOptimize(result.data());
benchmark::ClobberMemory();
}
state.SetBytesProcessed(state.iterations() * M * N * sizeof(Scalar));
}
// --- Broadcast + element-wise add (bias addition pattern) ---
static void BM_BroadcastAdd(benchmark::State& state) {
const int M = state.range(0);
const int N = state.range(1);
Tensor<Scalar, 2> mat(M, N);
Tensor<Scalar, 2> bias(1, N);
Tensor<Scalar, 2> result(M, N);
mat.setRandom();
bias.setRandom();
Eigen::array<int, 2> bcast = {M, 1};
for (auto _ : state) {
result = mat + bias.broadcast(bcast);
benchmark::DoNotOptimize(result.data());
benchmark::ClobberMemory();
}
state.SetBytesProcessed(state.iterations() * M * N * sizeof(Scalar) * 2);
}
// --- Rank-4 broadcast (batch x channels x 1 x 1) -> (batch x channels x H x W) ---
static void BM_BroadcastRank4(benchmark::State& state) {
const int batch = state.range(0);
const int C = state.range(1);
const int H = state.range(2);
Tensor<Scalar, 4> bias(batch, C, 1, 1);
Tensor<Scalar, 4> result(batch, C, H, H);
bias.setRandom();
Eigen::array<int, 4> bcast = {1, 1, H, H};
for (auto _ : state) {
result = bias.broadcast(bcast);
benchmark::DoNotOptimize(result.data());
benchmark::ClobberMemory();
}
state.SetBytesProcessed(state.iterations() * batch * C * H * H * sizeof(Scalar));
}
static void BroadcastSizes(::benchmark::Benchmark* b) {
for (int m : {64, 256, 1024}) {
for (int n : {64, 256, 1024}) {
b->Args({m, n});
}
}
}
static void Rank4Sizes(::benchmark::Benchmark* b) {
for (int batch : {1, 8}) {
for (int c : {64, 256}) {
for (int h : {16, 32}) {
b->Args({batch, c, h});
}
}
}
}
BENCHMARK(BM_BroadcastRow)->Apply(BroadcastSizes);
BENCHMARK(BM_BroadcastCol)->Apply(BroadcastSizes);
BENCHMARK(BM_BroadcastAdd)->Apply(BroadcastSizes);
BENCHMARK(BM_BroadcastRank4)->Apply(Rank4Sizes);

View File

@@ -0,0 +1,131 @@
// Benchmarks for Eigen Tensor coefficient-wise operations.
// Covers activation functions, normalization, and element-wise arithmetic.
#include <benchmark/benchmark.h>
#include <unsupported/Eigen/CXX11/Tensor>
using namespace Eigen;
typedef float Scalar;
// Macro to define a benchmark for a unary tensor operation.
#define BENCH_TENSOR_UNARY(NAME, EXPR) \
static void BM_##NAME(benchmark::State& state) { \
const int M = state.range(0); \
const int N = state.range(1); \
Tensor<Scalar, 2> a(M, N); \
a.setRandom(); \
Tensor<Scalar, 2> b(M, N); \
for (auto _ : state) { \
b = EXPR; \
benchmark::DoNotOptimize(b.data()); \
benchmark::ClobberMemory(); \
} \
state.SetBytesProcessed(state.iterations() * M * N * sizeof(Scalar) * 2); \
}
BENCH_TENSOR_UNARY(Exp, a.exp())
BENCH_TENSOR_UNARY(Log, a.abs().log())
BENCH_TENSOR_UNARY(Tanh, a.tanh())
BENCH_TENSOR_UNARY(Sigmoid, a.sigmoid())
BENCH_TENSOR_UNARY(ReLU, a.cwiseMax(Scalar(0)))
BENCH_TENSOR_UNARY(Sqrt, a.abs().sqrt())
// --- Element-wise binary operations ---
static void BM_Add(benchmark::State& state) {
const int M = state.range(0);
const int N = state.range(1);
Tensor<Scalar, 2> a(M, N);
Tensor<Scalar, 2> b(M, N);
Tensor<Scalar, 2> c(M, N);
a.setRandom();
b.setRandom();
for (auto _ : state) {
c = a + b;
benchmark::DoNotOptimize(c.data());
benchmark::ClobberMemory();
}
state.SetBytesProcessed(state.iterations() * M * N * sizeof(Scalar) * 3);
}
static void BM_Mul(benchmark::State& state) {
const int M = state.range(0);
const int N = state.range(1);
Tensor<Scalar, 2> a(M, N);
Tensor<Scalar, 2> b(M, N);
Tensor<Scalar, 2> c(M, N);
a.setRandom();
b.setRandom();
for (auto _ : state) {
c = a * b;
benchmark::DoNotOptimize(c.data());
benchmark::ClobberMemory();
}
state.SetBytesProcessed(state.iterations() * M * N * sizeof(Scalar) * 3);
}
// --- Fused multiply-add ---
static void BM_FMA(benchmark::State& state) {
const int M = state.range(0);
const int N = state.range(1);
Tensor<Scalar, 2> a(M, N);
Tensor<Scalar, 2> b(M, N);
Tensor<Scalar, 2> c(M, N);
Tensor<Scalar, 2> d(M, N);
a.setRandom();
b.setRandom();
c.setRandom();
for (auto _ : state) {
d = a * b + c;
benchmark::DoNotOptimize(d.data());
benchmark::ClobberMemory();
}
state.SetBytesProcessed(state.iterations() * M * N * sizeof(Scalar) * 4);
}
// --- Rank-4 coefficient-wise (CNN feature maps) ---
static void BM_ReLU_Rank4(benchmark::State& state) {
const int batch = state.range(0);
const int C = state.range(1);
const int H = state.range(2);
Tensor<Scalar, 4> a(batch, C, H, H);
Tensor<Scalar, 4> b(batch, C, H, H);
a.setRandom();
for (auto _ : state) {
b = a.cwiseMax(Scalar(0));
benchmark::DoNotOptimize(b.data());
benchmark::ClobberMemory();
}
state.SetBytesProcessed(state.iterations() * batch * C * H * H * sizeof(Scalar) * 2);
}
static void CwiseSizes(::benchmark::Benchmark* b) {
for (int size : {256, 1024}) {
b->Args({size, size});
}
}
static void Rank4Sizes(::benchmark::Benchmark* b) {
b->Args({32, 64, 16});
b->Args({8, 128, 32});
b->Args({1, 256, 64});
}
BENCHMARK(BM_Exp)->Apply(CwiseSizes);
BENCHMARK(BM_Log)->Apply(CwiseSizes);
BENCHMARK(BM_Tanh)->Apply(CwiseSizes);
BENCHMARK(BM_Sigmoid)->Apply(CwiseSizes);
BENCHMARK(BM_ReLU)->Apply(CwiseSizes);
BENCHMARK(BM_Sqrt)->Apply(CwiseSizes);
BENCHMARK(BM_Add)->Apply(CwiseSizes);
BENCHMARK(BM_Mul)->Apply(CwiseSizes);
BENCHMARK(BM_FMA)->Apply(CwiseSizes);
BENCHMARK(BM_ReLU_Rank4)->Apply(Rank4Sizes);

View File

@@ -0,0 +1,148 @@
// Benchmarks for Eigen Tensor contraction (generalized GEMM).
// Tests single-threaded (DefaultDevice) and multi-threaded (ThreadPoolDevice) variants.
#define EIGEN_USE_THREADS
#include <benchmark/benchmark.h>
#include <unsupported/Eigen/CXX11/Tensor>
#include <unsupported/Eigen/CXX11/ThreadPool>
using namespace Eigen;
#ifndef SCALAR
#define SCALAR float
#endif
typedef SCALAR Scalar;
// --- DefaultDevice contraction (rank-2, equivalent to matrix multiply) ---
static void BM_Contraction(benchmark::State& state) {
const int M = state.range(0);
const int N = state.range(1);
const int K = state.range(2);
Tensor<Scalar, 2> A(M, K);
Tensor<Scalar, 2> B(K, N);
Tensor<Scalar, 2> C(M, N);
A.setRandom();
B.setRandom();
using ContractDims = Tensor<Scalar, 2>::DimensionPair;
Eigen::array<ContractDims, 1> contract_dims = {ContractDims(1, 0)};
for (auto _ : state) {
C = A.contract(B, contract_dims);
benchmark::DoNotOptimize(C.data());
benchmark::ClobberMemory();
}
state.counters["GFLOPS"] =
benchmark::Counter(2.0 * M * N * K, benchmark::Counter::kIsIterationInvariantRate, benchmark::Counter::kIs1000);
}
// --- ThreadPoolDevice contraction ---
static void BM_Contraction_ThreadPool(benchmark::State& state) {
const int M = state.range(0);
const int N = state.range(1);
const int K = state.range(2);
const int threads = state.range(3);
Tensor<Scalar, 2> A(M, K);
Tensor<Scalar, 2> B(K, N);
Tensor<Scalar, 2> C(M, N);
A.setRandom();
B.setRandom();
ThreadPool tp(threads);
ThreadPoolDevice dev(&tp, threads);
using ContractDims = Tensor<Scalar, 2>::DimensionPair;
Eigen::array<ContractDims, 1> contract_dims = {ContractDims(1, 0)};
for (auto _ : state) {
C.device(dev) = A.contract(B, contract_dims);
benchmark::DoNotOptimize(C.data());
benchmark::ClobberMemory();
}
state.counters["GFLOPS"] =
benchmark::Counter(2.0 * M * N * K, benchmark::Counter::kIsIterationInvariantRate, benchmark::Counter::kIs1000);
state.counters["threads"] = threads;
}
// --- Rank-3 batch contraction ---
static void BM_BatchContraction(benchmark::State& state) {
const int batch = state.range(0);
const int M = state.range(1);
const int N = state.range(2);
const int K = state.range(3);
Tensor<Scalar, 3> A(batch, M, K);
Tensor<Scalar, 3> B(batch, K, N);
Tensor<Scalar, 3> C(batch, M, N);
A.setRandom();
B.setRandom();
using ContractDims = Tensor<Scalar, 3>::DimensionPair;
Eigen::array<ContractDims, 1> contract_dims = {ContractDims(2, 1)};
for (auto _ : state) {
C = A.contract(B, contract_dims);
benchmark::DoNotOptimize(C.data());
benchmark::ClobberMemory();
}
state.counters["GFLOPS"] = benchmark::Counter(2.0 * batch * M * N * K, benchmark::Counter::kIsIterationInvariantRate,
benchmark::Counter::kIs1000);
}
// --- RowMajor contraction ---
static void BM_Contraction_RowMajor(benchmark::State& state) {
const int M = state.range(0);
const int N = state.range(1);
const int K = state.range(2);
Tensor<Scalar, 2, RowMajor> A(M, K);
Tensor<Scalar, 2, RowMajor> B(K, N);
Tensor<Scalar, 2, RowMajor> C(M, N);
A.setRandom();
B.setRandom();
using ContractDims = Tensor<Scalar, 2, RowMajor>::DimensionPair;
Eigen::array<ContractDims, 1> contract_dims = {ContractDims(1, 0)};
for (auto _ : state) {
C = A.contract(B, contract_dims);
benchmark::DoNotOptimize(C.data());
benchmark::ClobberMemory();
}
state.counters["GFLOPS"] =
benchmark::Counter(2.0 * M * N * K, benchmark::Counter::kIsIterationInvariantRate, benchmark::Counter::kIs1000);
}
static void ContractionSizes(::benchmark::Benchmark* b) {
for (int size : {32, 64, 128, 256, 512, 1024}) {
b->Args({size, size, size});
}
// Non-square
b->Args({256, 256, 1024});
b->Args({1024, 64, 64});
}
static void ThreadPoolSizes(::benchmark::Benchmark* b) {
for (int size : {64, 256, 512, 1024}) {
for (int threads : {2, 4, 8}) {
b->Args({size, size, size, threads});
}
}
}
static void BatchSizes(::benchmark::Benchmark* b) {
for (int batch : {1, 8, 32}) {
for (int size : {64, 256}) {
b->Args({batch, size, size, size});
}
}
}
BENCHMARK(BM_Contraction)->Apply(ContractionSizes);
BENCHMARK(BM_Contraction_RowMajor)->Apply(ContractionSizes);
BENCHMARK(BM_Contraction_ThreadPool)->Apply(ThreadPoolSizes);
BENCHMARK(BM_BatchContraction)->Apply(BatchSizes);

View File

@@ -0,0 +1,151 @@
// Benchmarks for Eigen Tensor convolution (1D and 2D).
#define EIGEN_USE_THREADS
#include <benchmark/benchmark.h>
#include <unsupported/Eigen/CXX11/Tensor>
#include <unsupported/Eigen/CXX11/ThreadPool>
using namespace Eigen;
typedef float Scalar;
// --- 1D convolution ---
static void BM_Convolve1D(benchmark::State& state) {
const int input_size = state.range(0);
const int kernel_size = state.range(1);
Tensor<Scalar, 1> input(input_size);
Tensor<Scalar, 1> kernel(kernel_size);
input.setRandom();
kernel.setRandom();
Eigen::array<int, 1> dims = {0};
for (auto _ : state) {
Tensor<Scalar, 1> result = input.convolve(kernel, dims);
benchmark::DoNotOptimize(result.data());
benchmark::ClobberMemory();
}
double flops = 2.0 * (input_size - kernel_size + 1) * kernel_size;
state.counters["GFLOPS"] =
benchmark::Counter(flops, benchmark::Counter::kIsIterationInvariantRate, benchmark::Counter::kIs1000);
}
// --- 2D convolution ---
static void BM_Convolve2D(benchmark::State& state) {
const int H = state.range(0);
const int W = state.range(1);
const int kH = state.range(2);
const int kW = state.range(3);
Tensor<Scalar, 2> input(H, W);
Tensor<Scalar, 2> kernel(kH, kW);
input.setRandom();
kernel.setRandom();
Eigen::array<int, 2> dims = {0, 1};
for (auto _ : state) {
Tensor<Scalar, 2> result = input.convolve(kernel, dims);
benchmark::DoNotOptimize(result.data());
benchmark::ClobberMemory();
}
double flops = 2.0 * (H - kH + 1) * (W - kW + 1) * kH * kW;
state.counters["GFLOPS"] =
benchmark::Counter(flops, benchmark::Counter::kIsIterationInvariantRate, benchmark::Counter::kIs1000);
}
// --- 2D convolution with channels (rank-3: C x H x W, convolve on H,W) ---
static void BM_Convolve2D_Channels(benchmark::State& state) {
const int C = state.range(0);
const int H = state.range(1);
const int kH = state.range(2);
Tensor<Scalar, 3> input(C, H, H);
Tensor<Scalar, 2> kernel(kH, kH);
input.setRandom();
kernel.setRandom();
Eigen::array<int, 2> dims = {1, 2};
for (auto _ : state) {
Tensor<Scalar, 3> result = input.convolve(kernel, dims);
benchmark::DoNotOptimize(result.data());
benchmark::ClobberMemory();
}
int outH = H - kH + 1;
double flops = 2.0 * C * outH * outH * kH * kH;
state.counters["GFLOPS"] =
benchmark::Counter(flops, benchmark::Counter::kIsIterationInvariantRate, benchmark::Counter::kIs1000);
}
// --- 2D convolution with ThreadPool ---
static void BM_Convolve2D_ThreadPool(benchmark::State& state) {
const int H = state.range(0);
const int kH = state.range(1);
const int threads = state.range(2);
Tensor<Scalar, 2> input(H, H);
Tensor<Scalar, 2> kernel(kH, kH);
Tensor<Scalar, 2> result(H - kH + 1, H - kH + 1);
input.setRandom();
kernel.setRandom();
ThreadPool tp(threads);
ThreadPoolDevice dev(&tp, threads);
Eigen::array<int, 2> dims = {0, 1};
for (auto _ : state) {
result.device(dev) = input.convolve(kernel, dims);
benchmark::DoNotOptimize(result.data());
benchmark::ClobberMemory();
}
int outH = H - kH + 1;
double flops = 2.0 * outH * outH * kH * kH;
state.counters["GFLOPS"] =
benchmark::Counter(flops, benchmark::Counter::kIsIterationInvariantRate, benchmark::Counter::kIs1000);
state.counters["threads"] = threads;
}
static void Conv1DSizes(::benchmark::Benchmark* b) {
for (int input : {128, 512, 2048}) {
for (int kernel : {3, 5, 11}) {
b->Args({input, kernel});
}
}
}
static void Conv2DSizes(::benchmark::Benchmark* b) {
for (int hw : {32, 64, 128, 224}) {
for (int k : {3, 5, 7}) {
b->Args({hw, hw, k, k});
}
}
}
static void Conv2DChannelSizes(::benchmark::Benchmark* b) {
for (int c : {3, 64, 128}) {
for (int hw : {16, 32, 56}) {
for (int k : {3, 5}) {
b->Args({c, hw, k});
}
}
}
}
static void Conv2DThreadPoolSizes(::benchmark::Benchmark* b) {
for (int hw : {64, 128, 224}) {
for (int k : {3, 5}) {
for (int threads : {2, 4, 8}) {
b->Args({hw, k, threads});
}
}
}
}
BENCHMARK(BM_Convolve1D)->Apply(Conv1DSizes);
BENCHMARK(BM_Convolve2D)->Apply(Conv2DSizes);
BENCHMARK(BM_Convolve2D_Channels)->Apply(Conv2DChannelSizes);
BENCHMARK(BM_Convolve2D_ThreadPool)->Apply(Conv2DThreadPoolSizes);

View File

@@ -0,0 +1,142 @@
// Benchmarks for Eigen Tensor morphing operations: reshape, slice, chip, pad, stride.
#include <benchmark/benchmark.h>
#include <unsupported/Eigen/CXX11/Tensor>
using namespace Eigen;
typedef float Scalar;
// --- Reshape (zero-cost if no evaluation needed; force eval via assignment) ---
static void BM_Reshape(benchmark::State& state) {
const int M = state.range(0);
const int N = state.range(1);
Tensor<Scalar, 2> A(M, N);
A.setRandom();
Eigen::array<Index, 1> new_shape = {M * N};
for (auto _ : state) {
Tensor<Scalar, 1> B = A.reshape(new_shape);
benchmark::DoNotOptimize(B.data());
benchmark::ClobberMemory();
}
state.SetBytesProcessed(state.iterations() * M * N * sizeof(Scalar));
}
// --- Slice ---
static void BM_Slice(benchmark::State& state) {
const int M = state.range(0);
const int N = state.range(1);
Tensor<Scalar, 2> A(M, N);
A.setRandom();
int sliceM = M / 2;
int sliceN = N / 2;
Eigen::array<Index, 2> offsets = {0, 0};
Eigen::array<Index, 2> extents = {sliceM, sliceN};
for (auto _ : state) {
Tensor<Scalar, 2> B = A.slice(offsets, extents);
benchmark::DoNotOptimize(B.data());
benchmark::ClobberMemory();
}
state.SetBytesProcessed(state.iterations() * sliceM * sliceN * sizeof(Scalar));
}
// --- Chip (extract a sub-tensor along one dimension) ---
static void BM_Chip(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();
for (auto _ : state) {
Tensor<Scalar, 2> B = A.chip(0, 0);
benchmark::DoNotOptimize(B.data());
benchmark::ClobberMemory();
}
state.SetBytesProcessed(state.iterations() * D1 * D2 * sizeof(Scalar));
}
// --- Pad ---
static void BM_Pad(benchmark::State& state) {
const int M = state.range(0);
const int N = state.range(1);
const int padSize = state.range(2);
Tensor<Scalar, 2> A(M, N);
A.setRandom();
Eigen::array<std::pair<int, int>, 2> paddings;
paddings[0] = {padSize, padSize};
paddings[1] = {padSize, padSize};
for (auto _ : state) {
Tensor<Scalar, 2> B = A.pad(paddings);
benchmark::DoNotOptimize(B.data());
benchmark::ClobberMemory();
}
int outM = M + 2 * padSize;
int outN = N + 2 * padSize;
state.SetBytesProcessed(state.iterations() * outM * outN * sizeof(Scalar));
}
// --- Stride ---
static void BM_Stride(benchmark::State& state) {
const int M = state.range(0);
const int N = state.range(1);
const int stride = state.range(2);
Tensor<Scalar, 2> A(M, N);
A.setRandom();
Eigen::array<Index, 2> strides_arr = {stride, stride};
for (auto _ : state) {
Tensor<Scalar, 2> B = A.stride(strides_arr);
benchmark::DoNotOptimize(B.data());
benchmark::ClobberMemory();
}
int outM = (M + stride - 1) / stride;
int outN = (N + stride - 1) / stride;
state.SetBytesProcessed(state.iterations() * outM * outN * sizeof(Scalar));
}
static void MorphSizes(::benchmark::Benchmark* b) {
for (int size : {256, 1024}) {
b->Args({size, size});
}
}
static void ChipSizes(::benchmark::Benchmark* b) {
b->Args({32, 256, 256});
b->Args({64, 128, 128});
b->Args({8, 512, 512});
}
static void PadSizes(::benchmark::Benchmark* b) {
for (int size : {256, 1024}) {
for (int pad : {1, 4, 16}) {
b->Args({size, size, pad});
}
}
}
static void StrideSizes(::benchmark::Benchmark* b) {
for (int size : {256, 1024}) {
for (int stride : {2, 4}) {
b->Args({size, size, stride});
}
}
}
BENCHMARK(BM_Reshape)->Apply(MorphSizes);
BENCHMARK(BM_Slice)->Apply(MorphSizes);
BENCHMARK(BM_Chip)->Apply(ChipSizes);
BENCHMARK(BM_Pad)->Apply(PadSizes);
BENCHMARK(BM_Stride)->Apply(StrideSizes);

View File

@@ -0,0 +1,158 @@
// Benchmarks for Eigen Tensor reductions (sum, maximum, mean).
// Tests full and partial reductions, inner vs outer dimension, DefaultDevice and ThreadPoolDevice.
#define EIGEN_USE_THREADS
#include <benchmark/benchmark.h>
#include <unsupported/Eigen/CXX11/Tensor>
#include <unsupported/Eigen/CXX11/ThreadPool>
using namespace Eigen;
#ifndef SCALAR
#define SCALAR float
#endif
typedef SCALAR Scalar;
// --- Full reduction (rank-2) ---
template <typename ReduceOp>
static void BM_FullReduction(benchmark::State& state) {
const int M = state.range(0);
const int N = state.range(1);
Tensor<Scalar, 2> A(M, N);
A.setRandom();
for (auto _ : state) {
Tensor<Scalar, 0> result = A.reduce(Eigen::array<int, 2>{0, 1}, ReduceOp());
benchmark::DoNotOptimize(result.data());
benchmark::ClobberMemory();
}
state.SetBytesProcessed(state.iterations() * M * N * sizeof(Scalar));
}
// --- Partial reduction along dim 0 (inner dim, ColMajor) ---
static void BM_ReduceInner(benchmark::State& state) {
const int M = state.range(0);
const int N = state.range(1);
Tensor<Scalar, 2> A(M, N);
A.setRandom();
Eigen::array<int, 1> reduce_dims = {0};
for (auto _ : state) {
Tensor<Scalar, 1> result = A.sum(reduce_dims);
benchmark::DoNotOptimize(result.data());
benchmark::ClobberMemory();
}
state.SetBytesProcessed(state.iterations() * M * N * sizeof(Scalar));
}
// --- Partial reduction along dim 1 (outer dim, ColMajor) ---
static void BM_ReduceOuter(benchmark::State& state) {
const int M = state.range(0);
const int N = state.range(1);
Tensor<Scalar, 2> A(M, N);
A.setRandom();
Eigen::array<int, 1> reduce_dims = {1};
for (auto _ : state) {
Tensor<Scalar, 1> result = A.sum(reduce_dims);
benchmark::DoNotOptimize(result.data());
benchmark::ClobberMemory();
}
state.SetBytesProcessed(state.iterations() * M * N * sizeof(Scalar));
}
// --- Rank-4 partial reduction (batch x channels x H x W), reduce along spatial dims ---
static void BM_ReduceSpatial(benchmark::State& state) {
const int batch = state.range(0);
const int C = state.range(1);
const int H = state.range(2);
Tensor<Scalar, 4> A(batch, C, H, H);
A.setRandom();
Eigen::array<int, 2> reduce_dims = {2, 3};
for (auto _ : state) {
Tensor<Scalar, 2> result = A.sum(reduce_dims);
benchmark::DoNotOptimize(result.data());
benchmark::ClobberMemory();
}
state.SetBytesProcessed(state.iterations() * batch * C * H * H * sizeof(Scalar));
}
// --- Full reduction with ThreadPoolDevice ---
static void BM_FullReduction_ThreadPool(benchmark::State& state) {
const int M = state.range(0);
const int N = state.range(1);
const int threads = state.range(2);
Tensor<Scalar, 2> A(M, N);
Tensor<Scalar, 0> result;
A.setRandom();
ThreadPool tp(threads);
ThreadPoolDevice dev(&tp, threads);
for (auto _ : state) {
result.device(dev) = A.sum();
benchmark::DoNotOptimize(result.data());
benchmark::ClobberMemory();
}
state.SetBytesProcessed(state.iterations() * M * N * sizeof(Scalar));
state.counters["threads"] = threads;
}
// --- Maximum reduction (rank-2) ---
static void BM_MaxReduction(benchmark::State& state) {
const int M = state.range(0);
const int N = state.range(1);
Tensor<Scalar, 2> A(M, N);
A.setRandom();
for (auto _ : state) {
Tensor<Scalar, 0> result = A.maximum();
benchmark::DoNotOptimize(result.data());
benchmark::ClobberMemory();
}
state.SetBytesProcessed(state.iterations() * M * N * sizeof(Scalar));
}
static void ReductionSizes(::benchmark::Benchmark* b) {
for (int size : {64, 256, 1024}) {
b->Args({size, size});
}
}
static void ThreadPoolReductionSizes(::benchmark::Benchmark* b) {
for (int size : {256, 1024}) {
for (int threads : {2, 4, 8}) {
b->Args({size, size, threads});
}
}
}
static void SpatialSizes(::benchmark::Benchmark* b) {
for (int batch : {1, 8, 32}) {
for (int c : {64, 128}) {
for (int h : {16, 32}) {
b->Args({batch, c, h});
}
}
}
}
BENCHMARK(BM_FullReduction<internal::SumReducer<Scalar>>)->Apply(ReductionSizes)->Name("SumReduction");
BENCHMARK(BM_FullReduction<internal::MaxReducer<Scalar>>)->Apply(ReductionSizes)->Name("MaxReduction_Full");
BENCHMARK(BM_MaxReduction)->Apply(ReductionSizes);
BENCHMARK(BM_ReduceInner)->Apply(ReductionSizes);
BENCHMARK(BM_ReduceOuter)->Apply(ReductionSizes);
BENCHMARK(BM_ReduceSpatial)->Apply(SpatialSizes);
BENCHMARK(BM_FullReduction_ThreadPool)->Apply(ThreadPoolReductionSizes);

View File

@@ -0,0 +1,115 @@
// Benchmarks for Eigen Tensor shuffling (transpose / permutation).
#include <benchmark/benchmark.h>
#include <unsupported/Eigen/CXX11/Tensor>
using namespace Eigen;
typedef float Scalar;
// --- Rank-2 transpose ---
static void BM_Shuffle2D(benchmark::State& state) {
const int M = state.range(0);
const int N = state.range(1);
Tensor<Scalar, 2> A(M, N);
Tensor<Scalar, 2> B(N, M);
A.setRandom();
Eigen::array<int, 2> perm = {1, 0};
for (auto _ : state) {
B = A.shuffle(perm);
benchmark::DoNotOptimize(B.data());
benchmark::ClobberMemory();
}
state.SetBytesProcessed(state.iterations() * M * N * sizeof(Scalar) * 2);
}
// --- Identity shuffle (no permutation, measures overhead) ---
static void BM_ShuffleIdentity(benchmark::State& state) {
const int M = state.range(0);
const int N = state.range(1);
Tensor<Scalar, 2> A(M, N);
Tensor<Scalar, 2> B(M, N);
A.setRandom();
Eigen::array<int, 2> perm = {0, 1};
for (auto _ : state) {
B = A.shuffle(perm);
benchmark::DoNotOptimize(B.data());
benchmark::ClobberMemory();
}
state.SetBytesProcessed(state.iterations() * M * N * sizeof(Scalar) * 2);
}
// --- Rank-3 permutation ---
static void BM_Shuffle3D(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();
// Permutation (2, 0, 1)
Eigen::array<int, 3> perm = {2, 0, 1};
for (auto _ : state) {
Tensor<Scalar, 3> B = A.shuffle(perm);
benchmark::DoNotOptimize(B.data());
benchmark::ClobberMemory();
}
state.SetBytesProcessed(state.iterations() * D0 * D1 * D2 * sizeof(Scalar) * 2);
}
// --- Rank-4 permutation (NCHW -> NHWC layout conversion) ---
static void BM_Shuffle4D_NCHW_to_NHWC(benchmark::State& state) {
const int N = state.range(0);
const int C = state.range(1);
const int H = state.range(2);
Tensor<Scalar, 4> A(N, C, H, H);
A.setRandom();
// NCHW -> NHWC: permute (0, 2, 3, 1)
Eigen::array<int, 4> perm = {0, 2, 3, 1};
for (auto _ : state) {
Tensor<Scalar, 4> B = A.shuffle(perm);
benchmark::DoNotOptimize(B.data());
benchmark::ClobberMemory();
}
state.SetBytesProcessed(state.iterations() * N * C * H * H * sizeof(Scalar) * 2);
}
static void Shuffle2DSizes(::benchmark::Benchmark* b) {
for (int size : {256, 1024}) {
b->Args({size, size});
}
b->Args({64, 4096});
b->Args({4096, 64});
}
static void Shuffle3DSizes(::benchmark::Benchmark* b) {
b->Args({64, 64, 64});
b->Args({128, 128, 64});
b->Args({32, 256, 256});
}
static void Shuffle4DSizes(::benchmark::Benchmark* b) {
for (int batch : {1, 8}) {
for (int c : {3, 64}) {
for (int h : {32, 64}) {
b->Args({batch, c, h});
}
}
}
}
BENCHMARK(BM_Shuffle2D)->Apply(Shuffle2DSizes);
BENCHMARK(BM_ShuffleIdentity)->Apply(Shuffle2DSizes);
BENCHMARK(BM_Shuffle3D)->Apply(Shuffle3DSizes);
BENCHMARK(BM_Shuffle4D_NCHW_to_NHWC)->Apply(Shuffle4DSizes);

View File

@@ -0,0 +1,80 @@
// Benchmarks for Eigen Tensor FFT.
#include <benchmark/benchmark.h>
#include <unsupported/Eigen/CXX11/Tensor>
using namespace Eigen;
#ifndef SCALAR
#define SCALAR float
#endif
typedef SCALAR Scalar;
// --- 1D FFT ---
static void BM_TensorFFT_1D(benchmark::State& state) {
const int N = state.range(0);
Tensor<Scalar, 1> input(N);
input.setRandom();
Eigen::array<int, 1> fft_dims = {0};
for (auto _ : state) {
Tensor<std::complex<Scalar>, 1> result = input.template fft<BothParts, FFT_FORWARD>(fft_dims);
benchmark::DoNotOptimize(result.data());
benchmark::ClobberMemory();
}
double mflops = 5.0 * N * std::log2(static_cast<double>(N)) / 2.0; // real->complex
state.counters["MFLOPS"] =
benchmark::Counter(mflops, benchmark::Counter::kIsIterationInvariantRate, benchmark::Counter::kIs1000);
}
// --- 2D FFT ---
static void BM_TensorFFT_2D(benchmark::State& state) {
const int N = state.range(0);
Tensor<Scalar, 2> input(N, N);
input.setRandom();
Eigen::array<int, 2> fft_dims = {0, 1};
for (auto _ : state) {
Tensor<std::complex<Scalar>, 2> result = input.template fft<BothParts, FFT_FORWARD>(fft_dims);
benchmark::DoNotOptimize(result.data());
benchmark::ClobberMemory();
}
double total = N * N;
double mflops = 5.0 * total * std::log2(static_cast<double>(N));
state.counters["MFLOPS"] =
benchmark::Counter(mflops, benchmark::Counter::kIsIterationInvariantRate, benchmark::Counter::kIs1000);
}
// --- 1D inverse FFT ---
static void BM_TensorIFFT_1D(benchmark::State& state) {
const int N = state.range(0);
Tensor<std::complex<Scalar>, 1> input(N);
input.setRandom();
Eigen::array<int, 1> fft_dims = {0};
for (auto _ : state) {
Tensor<std::complex<Scalar>, 1> result = input.template fft<BothParts, FFT_REVERSE>(fft_dims);
benchmark::DoNotOptimize(result.data());
benchmark::ClobberMemory();
}
double mflops = 5.0 * N * std::log2(static_cast<double>(N));
state.counters["MFLOPS"] =
benchmark::Counter(mflops, benchmark::Counter::kIsIterationInvariantRate, benchmark::Counter::kIs1000);
}
static void FFTSizes(::benchmark::Benchmark* b) {
for (int n : {64, 256, 1024, 4096}) {
b->Arg(n);
}
}
BENCHMARK(BM_TensorFFT_1D)->Apply(FFTSizes);
BENCHMARK(BM_TensorFFT_2D)->Apply(FFTSizes);
BENCHMARK(BM_TensorIFFT_1D)->Apply(FFTSizes);