blob: 9d78923d9fbc7580c5edf8a266b4a7c4949b73c2 [file]
// Benchmarks for the PacketMath implementations of `plset`, `ploaddup`,
// `ploadquad`, `predux_mul`, and `ptranspose`, at the packet-op level and
// shared across whichever architecture backend the build targets. To
// compare against a prior implementation, build and run this same file
// against the Eigen checkout in question -- it only calls the public
// `Eigen::internal` packet API, so it is source-compatible with whatever
// PacketMath.h happens to provide.
// SPDX-FileCopyrightText: The Eigen Authors
// SPDX-License-Identifier: MPL-2.0
#include <benchmark/benchmark.h>
#include <Eigen/Core>
#include <cmath>
#include <cstdint>
#include <type_traits>
namespace Eigen {
namespace {
using internal::packet_traits;
using internal::PacketBlock;
using internal::pfirst;
using internal::ploadu;
using internal::pstoreu;
template <typename Packet, int N>
EIGEN_DONT_INLINE void call_ptranspose(PacketBlock<Packet, N>& kernel) {
internal::ptranspose(kernel);
}
// ---- plset ----
template <typename Scalar>
void BM_Plset(benchmark::State& state) {
using Packet = typename packet_traits<Scalar>::type;
constexpr int N = packet_traits<Scalar>::size;
Scalar a = Scalar(7);
Scalar out[N];
pstoreu(out, internal::plset<Packet>(a));
for (int i = 0; i < N; ++i) {
if (out[i] != static_cast<Scalar>(a + i)) {
state.SkipWithError("Plset: materialized result does not match scalar reference");
return;
}
}
benchmark::DoNotOptimize(a);
for (auto _ : state) {
pstoreu(out, internal::plset<Packet>(a));
benchmark::DoNotOptimize(out);
}
}
BENCHMARK(BM_Plset<numext::int32_t>)->Name("Plset_int32");
BENCHMARK(BM_Plset<float>)->Name("Plset_float");
BENCHMARK(BM_Plset<double>)->Name("Plset_double");
// ---- ploaddup ----
template <typename Scalar>
void BM_Ploaddup(benchmark::State& state) {
using Packet = typename packet_traits<Scalar>::type;
constexpr int N = packet_traits<Scalar>::size;
Scalar in[N];
for (int i = 0; i < N; ++i) in[i] = static_cast<Scalar>(i);
Scalar out[N];
pstoreu(out, internal::ploaddup<Packet>(in));
for (int i = 0; i < N; ++i) {
if (out[i] != in[i / 2]) {
state.SkipWithError("Ploaddup: materialized result does not match scalar reference");
return;
}
}
benchmark::DoNotOptimize(in);
for (auto _ : state) {
pstoreu(out, internal::ploaddup<Packet>(in));
benchmark::DoNotOptimize(out);
}
}
BENCHMARK(BM_Ploaddup<numext::int32_t>)->Name("Ploaddup_int32");
BENCHMARK(BM_Ploaddup<float>)->Name("Ploaddup_float");
BENCHMARK(BM_Ploaddup<double>)->Name("Ploaddup_double");
// ---- ploadquad ----
template <typename Scalar>
void BM_Ploadquad(benchmark::State& state) {
using Packet = typename packet_traits<Scalar>::type;
constexpr int N = packet_traits<Scalar>::size;
Scalar in[N];
for (int i = 0; i < N; ++i) in[i] = static_cast<Scalar>(i);
Scalar out[N];
pstoreu(out, internal::ploadquad<Packet>(in));
for (int i = 0; i < N; ++i) {
if (out[i] != in[i / 4]) {
state.SkipWithError("Ploadquad: materialized result does not match scalar reference");
return;
}
}
benchmark::DoNotOptimize(in);
for (auto _ : state) {
pstoreu(out, internal::ploadquad<Packet>(in));
benchmark::DoNotOptimize(out);
}
}
BENCHMARK(BM_Ploadquad<numext::int32_t>)->Name("Ploadquad_int32");
BENCHMARK(BM_Ploadquad<float>)->Name("Ploadquad_float");
BENCHMARK(BM_Ploadquad<double>)->Name("Ploadquad_double");
// ---- predux_mul ----
// Inputs are chosen so the true product is exactly representable regardless
// of the order the reduction folds lanes together, at every vector length:
// powers of two multiply without rounding, and centering the exponents around
// zero keeps both the inputs and the product in range for float/double even
// at N == 64; multiplying by 1 is exact and overflow-free for int32.
template <typename Scalar>
void fill_redux_mul_input(Scalar (&in)[packet_traits<Scalar>::size], Scalar& expected) {
constexpr int N = packet_traits<Scalar>::size;
if constexpr (std::is_integral<Scalar>::value) {
for (int i = 0; i < N; ++i) in[i] = Scalar(1);
in[0] = Scalar(-3);
expected = Scalar(-3);
if (N > 1) {
in[N - 1] = Scalar(2);
expected = Scalar(-6);
}
} else {
for (int i = 0; i < N; ++i) in[i] = std::ldexp(Scalar(1), i - N / 2);
expected = std::ldexp(Scalar(1), -N / 2);
}
}
template <typename Scalar>
void BM_ReduxMul(benchmark::State& state) {
using Packet = typename packet_traits<Scalar>::type;
constexpr int N = packet_traits<Scalar>::size;
Scalar in[N];
Scalar expected;
fill_redux_mul_input<Scalar>(in, expected);
Packet a = ploadu<Packet>(in);
if (internal::predux_mul<Packet>(a) != expected) {
state.SkipWithError("ReduxMul: materialized result does not match scalar reference");
return;
}
benchmark::DoNotOptimize(a);
for (auto _ : state) benchmark::DoNotOptimize(internal::predux_mul<Packet>(a));
}
BENCHMARK(BM_ReduxMul<numext::int32_t>)->Name("ReduxMul_int32");
BENCHMARK(BM_ReduxMul<float>)->Name("ReduxMul_float");
BENCHMARK(BM_ReduxMul<double>)->Name("ReduxMul_double");
// ---- ptranspose ----
// Benchmarked at N == the type's packet width, i.e. a full square transpose,
// so the expected result is simply new_packet[i][k] == old_packet[k][i].
// Correctness is checked once on a scratch kernel -- ptranspose applied
// repeatedly toggles between the original and transposed state, so checking
// after the timed loop would depend on the (unpredictable) iteration count.
//
// call_ptranspose is deliberately EIGEN_DONT_INLINE: real callers inline
// ptranspose on register-resident packets, but this wrapper forces the block
// through memory, so this benchmark understates the win; transposeInPlace is
// the macro benchmark for the inlined case.
template <typename Scalar>
void BM_Ptranspose(benchmark::State& state) {
using Packet = typename packet_traits<Scalar>::type;
constexpr int N = packet_traits<Scalar>::size;
Scalar in[N * N];
for (int i = 0; i < N * N; ++i) in[i] = static_cast<Scalar>(i);
PacketBlock<Packet, N> check;
for (int i = 0; i < N; ++i) check.packet[i] = ploadu<Packet>(in + i * N);
call_ptranspose<Packet, N>(check);
for (int i = 0; i < N; ++i) {
Scalar row[N];
pstoreu(row, check.packet[i]);
for (int k = 0; k < N; ++k) {
if (row[k] != in[k * N + i]) {
state.SkipWithError("Ptranspose: materialized result does not match scalar reference");
return;
}
}
}
PacketBlock<Packet, N> kernel;
for (int i = 0; i < N; ++i) kernel.packet[i] = ploadu<Packet>(in + i * N);
for (auto _ : state) {
call_ptranspose<Packet, N>(kernel);
benchmark::DoNotOptimize(pfirst<Packet>(kernel.packet[0]));
}
}
BENCHMARK(BM_Ptranspose<numext::int32_t>)->Name("Ptranspose_int32");
BENCHMARK(BM_Ptranspose<float>)->Name("Ptranspose_float");
BENCHMARK(BM_Ptranspose<double>)->Name("Ptranspose_double");
} // namespace
} // namespace Eigen