| // 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 |