Files
eigen/benchmarks/Core/bench_broadcasting.cpp
T
2026-08-11 07:12:13 -07:00

239 lines
9.3 KiB
C++

// Benchmarks for colwise/rowwise reductions and broadcasting operations.
//
// Tests vectorwise reductions (sum, mean, norm, minCoeff, maxCoeff) and
// broadcasting arithmetic (rowwise += vec, colwise -= vec, rowwise *= vec).
// The BroadcastSubExp pair isolates Eigen's packet path through the Replicate
// evaluator: the scalar fallback calls libm exp per element, so unlike the
// plain add/mul broadcasts it cannot be rescued by compiler auto-vectorization.
// The PerCol variant performs the same operation with an explicit column loop
// and serves as an upper-bound reference.
// SPDX-FileCopyrightText: The Eigen Authors
// SPDX-License-Identifier: MPL-2.0
#include <benchmark/benchmark.h>
#include <Eigen/Core>
using namespace Eigen;
// --- Colwise reductions (reduce each column to a scalar) ---
template <typename Scalar>
static void BM_ColwiseSum(benchmark::State& state) {
const Index rows = state.range(0);
const Index cols = state.range(1);
using Mat = Matrix<Scalar, Dynamic, Dynamic>;
Mat m = Mat::Random(rows, cols);
Matrix<Scalar, 1, Dynamic> result(cols);
for (auto _ : state) {
result = m.colwise().sum();
benchmark::DoNotOptimize(result.data());
}
state.SetBytesProcessed(state.iterations() * rows * cols * sizeof(Scalar));
}
template <typename Scalar>
static void BM_ColwiseMean(benchmark::State& state) {
const Index rows = state.range(0);
const Index cols = state.range(1);
using Mat = Matrix<Scalar, Dynamic, Dynamic>;
Mat m = Mat::Random(rows, cols);
Matrix<Scalar, 1, Dynamic> result(cols);
for (auto _ : state) {
result = m.colwise().mean();
benchmark::DoNotOptimize(result.data());
}
state.SetBytesProcessed(state.iterations() * rows * cols * sizeof(Scalar));
}
template <typename Scalar>
static void BM_ColwiseNorm(benchmark::State& state) {
const Index rows = state.range(0);
const Index cols = state.range(1);
using Mat = Matrix<Scalar, Dynamic, Dynamic>;
Mat m = Mat::Random(rows, cols);
Matrix<Scalar, 1, Dynamic> result(cols);
for (auto _ : state) {
result = m.colwise().norm();
benchmark::DoNotOptimize(result.data());
}
state.SetBytesProcessed(state.iterations() * rows * cols * sizeof(Scalar));
}
template <typename Scalar>
static void BM_ColwiseMinCoeff(benchmark::State& state) {
const Index rows = state.range(0);
const Index cols = state.range(1);
using Mat = Matrix<Scalar, Dynamic, Dynamic>;
Mat m = Mat::Random(rows, cols);
Matrix<Scalar, 1, Dynamic> result(cols);
for (auto _ : state) {
result = m.colwise().minCoeff();
benchmark::DoNotOptimize(result.data());
}
state.SetBytesProcessed(state.iterations() * rows * cols * sizeof(Scalar));
}
template <typename Scalar>
static void BM_ColwiseMaxCoeff(benchmark::State& state) {
const Index rows = state.range(0);
const Index cols = state.range(1);
using Mat = Matrix<Scalar, Dynamic, Dynamic>;
Mat m = Mat::Random(rows, cols);
Matrix<Scalar, 1, Dynamic> result(cols);
for (auto _ : state) {
result = m.colwise().maxCoeff();
benchmark::DoNotOptimize(result.data());
}
state.SetBytesProcessed(state.iterations() * rows * cols * sizeof(Scalar));
}
// --- Rowwise reductions (reduce each row to a scalar) ---
template <typename Scalar>
static void BM_RowwiseSum(benchmark::State& state) {
const Index rows = state.range(0);
const Index cols = state.range(1);
using Mat = Matrix<Scalar, Dynamic, Dynamic>;
Mat m = Mat::Random(rows, cols);
Matrix<Scalar, Dynamic, 1> result(rows);
for (auto _ : state) {
result = m.rowwise().sum();
benchmark::DoNotOptimize(result.data());
}
state.SetBytesProcessed(state.iterations() * rows * cols * sizeof(Scalar));
}
template <typename Scalar>
static void BM_RowwiseNorm(benchmark::State& state) {
const Index rows = state.range(0);
const Index cols = state.range(1);
using Mat = Matrix<Scalar, Dynamic, Dynamic>;
Mat m = Mat::Random(rows, cols);
Matrix<Scalar, Dynamic, 1> result(rows);
for (auto _ : state) {
result = m.rowwise().norm();
benchmark::DoNotOptimize(result.data());
}
state.SetBytesProcessed(state.iterations() * rows * cols * sizeof(Scalar));
}
// --- Broadcasting operations ---
template <typename Scalar>
static void BM_RowwiseBroadcastAdd(benchmark::State& state) {
const Index rows = state.range(0);
const Index cols = state.range(1);
using Mat = Matrix<Scalar, Dynamic, Dynamic>;
using Vec = Matrix<Scalar, 1, Dynamic>;
Mat m = Mat::Random(rows, cols);
Vec v = Vec::Random(cols);
for (auto _ : state) {
m.noalias() = m.rowwise() + v;
benchmark::DoNotOptimize(m.data());
}
state.SetBytesProcessed(state.iterations() * rows * cols * sizeof(Scalar) * 2);
}
template <typename Scalar>
static void BM_ColwiseBroadcastAdd(benchmark::State& state) {
const Index rows = state.range(0);
const Index cols = state.range(1);
using Mat = Matrix<Scalar, Dynamic, Dynamic>;
using Vec = Matrix<Scalar, Dynamic, 1>;
Mat m = Mat::Random(rows, cols);
Vec v = Vec::Random(rows);
for (auto _ : state) {
m.noalias() = m.colwise() + v;
benchmark::DoNotOptimize(m.data());
}
state.SetBytesProcessed(state.iterations() * rows * cols * sizeof(Scalar) * 2);
}
template <typename Scalar>
static void BM_RowwiseBroadcastMul(benchmark::State& state) {
const Index rows = state.range(0);
const Index cols = state.range(1);
using Mat = Matrix<Scalar, Dynamic, Dynamic>;
Mat m = Mat::Random(rows, cols);
Array<Scalar, 1, Dynamic> v = Array<Scalar, 1, Dynamic>::Random(cols);
for (auto _ : state) {
m.array().rowwise() *= v;
benchmark::DoNotOptimize(m.data());
}
state.SetBytesProcessed(state.iterations() * rows * cols * sizeof(Scalar) * 2);
}
// Broadcast feeding a transcendental (softmax-style normalization):
// out = exp(m - v replicated across columns).
template <typename Scalar>
static void BM_ColwiseBroadcastSubExp(benchmark::State& state) {
const Index rows = state.range(0);
const Index cols = state.range(1);
using Mat = Matrix<Scalar, Dynamic, Dynamic>;
using Vec = Matrix<Scalar, Dynamic, 1>;
Mat m = Mat::Random(rows, cols);
Vec v = Vec::Random(rows);
Mat out(rows, cols);
for (auto _ : state) {
out = (m.colwise() - v).array().exp();
benchmark::DoNotOptimize(out.data());
}
state.SetBytesProcessed(state.iterations() * rows * cols * sizeof(Scalar) * 2);
}
// Same operation with an explicit per-column loop: upper-bound reference for
// BM_ColwiseBroadcastSubExp.
template <typename Scalar>
static void BM_PerColBroadcastSubExp(benchmark::State& state) {
const Index rows = state.range(0);
const Index cols = state.range(1);
using Mat = Matrix<Scalar, Dynamic, Dynamic>;
using Vec = Matrix<Scalar, Dynamic, 1>;
Mat m = Mat::Random(rows, cols);
Vec v = Vec::Random(rows);
Mat out(rows, cols);
for (auto _ : state) {
for (Index j = 0; j < cols; ++j) out.col(j) = (m.col(j) - v).array().exp();
benchmark::DoNotOptimize(out.data());
}
state.SetBytesProcessed(state.iterations() * rows * cols * sizeof(Scalar) * 2);
}
// --- Size configurations ---
// clang-format off
// Square matrices; tall-thin (many rows, few cols); short-wide (few rows, many cols).
#define BROADCAST_SIZES \
->Args({64, 64})->Args({128, 128})->Args({256, 256})->Args({512, 512})->Args({1024, 1024}) \
->Args({10000, 32})->Args({32, 10000})
// --- Register: float ---
BENCHMARK(BM_ColwiseSum<float>) BROADCAST_SIZES ->Name("ColwiseSum_float");
BENCHMARK(BM_ColwiseMean<float>) BROADCAST_SIZES ->Name("ColwiseMean_float");
BENCHMARK(BM_ColwiseNorm<float>) BROADCAST_SIZES ->Name("ColwiseNorm_float");
BENCHMARK(BM_ColwiseMinCoeff<float>) BROADCAST_SIZES ->Name("ColwiseMinCoeff_float");
BENCHMARK(BM_ColwiseMaxCoeff<float>) BROADCAST_SIZES ->Name("ColwiseMaxCoeff_float");
BENCHMARK(BM_RowwiseSum<float>) BROADCAST_SIZES ->Name("RowwiseSum_float");
BENCHMARK(BM_RowwiseNorm<float>) BROADCAST_SIZES ->Name("RowwiseNorm_float");
BENCHMARK(BM_RowwiseBroadcastAdd<float>) BROADCAST_SIZES ->Name("RowwiseBroadcastAdd_float");
BENCHMARK(BM_ColwiseBroadcastAdd<float>) BROADCAST_SIZES ->Name("ColwiseBroadcastAdd_float");
BENCHMARK(BM_RowwiseBroadcastMul<float>) BROADCAST_SIZES ->Name("RowwiseBroadcastMul_float");
BENCHMARK(BM_ColwiseBroadcastSubExp<float>) BROADCAST_SIZES ->Name("ColwiseBroadcastSubExp_float");
BENCHMARK(BM_PerColBroadcastSubExp<float>) BROADCAST_SIZES ->Name("PerColBroadcastSubExp_float");
// --- Register: double ---
BENCHMARK(BM_ColwiseSum<double>) BROADCAST_SIZES ->Name("ColwiseSum_double");
BENCHMARK(BM_ColwiseMean<double>) BROADCAST_SIZES ->Name("ColwiseMean_double");
BENCHMARK(BM_ColwiseNorm<double>) BROADCAST_SIZES ->Name("ColwiseNorm_double");
BENCHMARK(BM_ColwiseMinCoeff<double>) BROADCAST_SIZES ->Name("ColwiseMinCoeff_double");
BENCHMARK(BM_ColwiseMaxCoeff<double>) BROADCAST_SIZES ->Name("ColwiseMaxCoeff_double");
BENCHMARK(BM_RowwiseSum<double>) BROADCAST_SIZES ->Name("RowwiseSum_double");
BENCHMARK(BM_RowwiseNorm<double>) BROADCAST_SIZES ->Name("RowwiseNorm_double");
BENCHMARK(BM_RowwiseBroadcastAdd<double>) BROADCAST_SIZES ->Name("RowwiseBroadcastAdd_double");
BENCHMARK(BM_ColwiseBroadcastAdd<double>) BROADCAST_SIZES ->Name("ColwiseBroadcastAdd_double");
BENCHMARK(BM_RowwiseBroadcastMul<double>) BROADCAST_SIZES ->Name("RowwiseBroadcastMul_double");
BENCHMARK(BM_ColwiseBroadcastSubExp<double>) BROADCAST_SIZES ->Name("ColwiseBroadcastSubExp_double");
BENCHMARK(BM_PerColBroadcastSubExp<double>) BROADCAST_SIZES ->Name("PerColBroadcastSubExp_double");
#undef BROADCAST_SIZES
// clang-format on