| // Benchmarks for chained tensor expressions with ThreadPool. | |
| // Tests realistic compound expressions spanning memory-bound to compute-bound. | |
| // SPDX-FileCopyrightText: The Eigen Authors | |
| // SPDX-License-Identifier: MPL-2.0 | |
| #define EIGEN_USE_THREADS | |
| #include <benchmark/benchmark.h> | |
| #include <contrib/Eigen/Tensor> | |
| #include <Eigen/ThreadPool> | |
| using namespace Eigen; | |
| typedef float Scalar; | |
| // --- Pure memory-bound baseline (dst = src) --- | |
| static void BM_Copy_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> src(M, N); | |
| Tensor<Scalar, 2> dst(M, N); | |
| src.setRandom(); | |
| ThreadPool tp(threads); | |
| ThreadPoolDevice dev(&tp, threads); | |
| for (auto _ : state) { | |
| dst.device(dev) = src; | |
| benchmark::DoNotOptimize(dst.data()); | |
| benchmark::ClobberMemory(); | |
| } | |
| state.SetBytesProcessed(state.iterations() * M * N * sizeof(Scalar) * 2); | |
| state.counters["threads"] = threads; | |
| } | |
| // --- Near-memory-bound: bias + ReLU --- | |
| // Pattern: (x + bias.broadcast()).cwiseMax(0) | |
| static void BM_BiasReLU_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> x(M, N); | |
| Tensor<Scalar, 2> bias(1, N); | |
| Tensor<Scalar, 2> result(M, N); | |
| x.setRandom(); | |
| bias.setRandom(); | |
| ThreadPool tp(threads); | |
| ThreadPoolDevice dev(&tp, threads); | |
| Eigen::array<int, 2> bcast = {M, 1}; | |
| for (auto _ : state) { | |
| result.device(dev) = (x + bias.broadcast(bcast)).cwiseMax(Scalar(0)); | |
| benchmark::DoNotOptimize(result.data()); | |
| benchmark::ClobberMemory(); | |
| } | |
| state.SetBytesProcessed(state.iterations() * M * N * sizeof(Scalar) * 2); | |
| state.counters["threads"] = threads; | |
| } | |
| // --- Compute-bound: Horner polynomial ((a*x+b)*x+c)*x+d --- | |
| static void BM_Polynomial_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> x(M, N); | |
| Tensor<Scalar, 2> result(M, N); | |
| x.setRandom(); | |
| ThreadPool tp(threads); | |
| ThreadPoolDevice dev(&tp, threads); | |
| const Scalar a = 0.5f, b = 1.2f, c = -0.3f, d = 0.7f; | |
| for (auto _ : state) { | |
| result.device(dev) = ((x.constant(a) * x + x.constant(b)) * x + x.constant(c)) * x + x.constant(d); | |
| benchmark::DoNotOptimize(result.data()); | |
| benchmark::ClobberMemory(); | |
| } | |
| state.SetBytesProcessed(state.iterations() * M * N * sizeof(Scalar) * 2); | |
| state.counters["threads"] = threads; | |
| } | |
| // --- Compute-bound: exp (expensive transcendental) --- | |
| static void BM_ExpNormalize_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> x(M, N); | |
| Tensor<Scalar, 2> result(M, N); | |
| x.setRandom(); | |
| ThreadPool tp(threads); | |
| ThreadPoolDevice dev(&tp, threads); | |
| for (auto _ : state) { | |
| result.device(dev) = x.exp(); | |
| benchmark::DoNotOptimize(result.data()); | |
| benchmark::ClobberMemory(); | |
| } | |
| state.SetBytesProcessed(state.iterations() * M * N * sizeof(Scalar) * 2); | |
| state.counters["threads"] = threads; | |
| } | |
| // --- Batch normalization: gamma * (x - mean) / sqrt(var + eps) + beta --- | |
| static void BM_BatchNorm_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> x(M, N); | |
| Tensor<Scalar, 2> result(M, N); | |
| Tensor<Scalar, 2> gamma(1, N); | |
| Tensor<Scalar, 2> beta(1, N); | |
| Tensor<Scalar, 2> mean(1, N); | |
| Tensor<Scalar, 2> var(1, N); | |
| x.setRandom(); | |
| gamma.setRandom(); | |
| beta.setRandom(); | |
| mean.setRandom(); | |
| var.setRandom(); | |
| var = var.abs() + var.constant(Scalar(0.1)); | |
| ThreadPool tp(threads); | |
| ThreadPoolDevice dev(&tp, threads); | |
| Eigen::array<int, 2> bcast = {M, 1}; | |
| const Scalar eps = 1e-5f; | |
| for (auto _ : state) { | |
| result.device(dev) = | |
| gamma.broadcast(bcast) * (x - mean.broadcast(bcast)) * (var.broadcast(bcast) + x.constant(eps)).rsqrt() + | |
| beta.broadcast(bcast); | |
| benchmark::DoNotOptimize(result.data()); | |
| benchmark::ClobberMemory(); | |
| } | |
| state.SetBytesProcessed(state.iterations() * M * N * sizeof(Scalar) * 2); | |
| state.counters["threads"] = threads; | |
| } | |
| // clang-format off | |
| #define CHAINED_SIZES \ | |
| ->Args({256, 256, 1})->Args({256, 256, 2})->Args({256, 256, 4}) \ | |
| ->Args({256, 256, 8})->Args({256, 256, 12})->Args({256, 256, 16}) \ | |
| ->Args({1024, 1024, 1})->Args({1024, 1024, 2})->Args({1024, 1024, 4}) \ | |
| ->Args({1024, 1024, 8})->Args({1024, 1024, 12})->Args({1024, 1024, 16}) \ | |
| ->Args({4096, 4096, 1})->Args({4096, 4096, 2})->Args({4096, 4096, 4}) \ | |
| ->Args({4096, 4096, 8})->Args({4096, 4096, 12})->Args({4096, 4096, 16}) | |
| // clang-format on | |
| BENCHMARK(BM_Copy_ThreadPool) CHAINED_SIZES->UseRealTime(); | |
| BENCHMARK(BM_BiasReLU_ThreadPool) CHAINED_SIZES->UseRealTime(); | |
| BENCHMARK(BM_Polynomial_ThreadPool) CHAINED_SIZES->UseRealTime(); | |
| BENCHMARK(BM_ExpNormalize_ThreadPool) CHAINED_SIZES->UseRealTime(); | |
| BENCHMARK(BM_BatchNorm_ThreadPool) CHAINED_SIZES->UseRealTime(); |