Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
237 changes: 237 additions & 0 deletions benchmarks/01_trivial/18_gather_scatter.cpp

Large diffs are not rendered by default.

108 changes: 108 additions & 0 deletions benchmarks/01_trivial/18_gather_scatter.ispc
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
// Copyright (c) 2026, Intel Corporation
// SPDX-License-Identifier: BSD-3-Clause

export uniform int width() { return programCount; }

// Random patterns: dst[i] = src[idx[i]] and dst[idx[i]] = src[i], with an index array of type IT.
#define GATHER_RANDOM(ET, IT) \
export void gather_random_##ET##_##IT(uniform ET *uniform src, uniform IT *uniform idx, uniform ET *uniform dst, \
uniform int n) { \
foreach (i = 0 ... n) { \
dst[i] = src[idx[i]]; \
} \
}

#define SCATTER_RANDOM(ET, IT) \
export void scatter_random_##ET##_##IT(uniform ET *uniform src, uniform IT *uniform idx, uniform ET *uniform dst, \
uniform int n) { \
foreach (i = 0 ... n) { \
dst[idx[i]] = src[i]; \
} \
}

// The same with a partial execution mask: only the elements with on[i] != 0.
#define GATHER_RANDOM_MASKED(ET, IT) \
export void gather_random_masked_##ET##_##IT(uniform ET *uniform src, uniform IT *uniform idx, \
uniform int8 *uniform on, uniform ET *uniform dst, uniform int n) { \
foreach (i = 0 ... n) { \
if (on[i]) { \
dst[i] = src[idx[i]]; \
} \
} \
}

#define SCATTER_RANDOM_MASKED(ET, IT) \
export void scatter_random_masked_##ET##_##IT(uniform ET *uniform src, uniform IT *uniform idx, \
uniform int8 *uniform on, uniform ET *uniform dst, uniform int n) { \
foreach (i = 0 ... n) { \
if (on[i]) { \
dst[idx[i]] = src[i]; \
} \
} \
}

// Patterns the compiler can see: every block of 4 elements reads its first element, reverse order, stride 2 and 4.
#define GATHER_PATTERNS(ET) \
export void gather_broadcast4_##ET(uniform ET *uniform src, uniform ET *uniform dst, uniform int n) { \
foreach (i = 0 ... n) { \
dst[i] = src[i & ~3]; \
} \
} \
export void gather_reverse_##ET(uniform ET *uniform src, uniform ET *uniform dst, uniform int n) { \
foreach (i = 0 ... n) { \
dst[i] = src[n - 1 - i]; \
} \
} \
export void gather_stride2_##ET(uniform ET *uniform src, uniform ET *uniform dst, uniform int n) { \
foreach (i = 0 ... n) { \
dst[i] = src[2 * i]; \
} \
} \
export void gather_stride4_##ET(uniform ET *uniform src, uniform ET *uniform dst, uniform int n) { \
foreach (i = 0 ... n) { \
dst[i] = src[4 * i]; \
} \
}

#define SCATTER_PATTERNS(ET) \
export void scatter_reverse_##ET(uniform ET *uniform src, uniform ET *uniform dst, uniform int n) { \
foreach (i = 0 ... n) { \
dst[n - 1 - i] = src[i]; \
} \
} \
export void scatter_stride2_##ET(uniform ET *uniform src, uniform ET *uniform dst, uniform int n) { \
foreach (i = 0 ... n) { \
dst[2 * i] = src[i]; \
} \
} \
export void scatter_stride4_##ET(uniform ET *uniform src, uniform ET *uniform dst, uniform int n) { \
foreach (i = 0 ... n) { \
dst[4 * i] = src[i]; \
} \
}

GATHER_RANDOM(int32, int32)
GATHER_RANDOM(int32, uint32)
GATHER_RANDOM(int32, int16)
GATHER_RANDOM(int32, uint16)
GATHER_RANDOM(int8, int32)
GATHER_RANDOM(int8, uint32)
GATHER_RANDOM(double, int32)
GATHER_RANDOM(double, uint32)

SCATTER_RANDOM(int32, int32)
SCATTER_RANDOM(int32, uint32)
SCATTER_RANDOM(int32, int16)
SCATTER_RANDOM(int8, int32)
SCATTER_RANDOM(double, int32)

GATHER_RANDOM_MASKED(int32, int32)
GATHER_RANDOM_MASKED(double, int32)
SCATTER_RANDOM_MASKED(int32, int32)

GATHER_PATTERNS(int8)
GATHER_PATTERNS(int32)
GATHER_PATTERNS(double)

SCATTER_PATTERNS(int32)
SCATTER_PATTERNS(double)
1 change: 1 addition & 0 deletions benchmarks/01_trivial/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,4 @@ compile_benchmark_test(14_shift)
compile_benchmark_test(15_reduce_equal)
compile_benchmark_test(16_dispatcher_overhead DISPATCH)
compile_benchmark_test(17_pdep_pext)
compile_benchmark_test(18_gather_scatter)
112 changes: 112 additions & 0 deletions benchmarks/03_complex/01_gather_aos.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
// Copyright (c) 2026, Intel Corporation
// SPDX-License-Identifier: BSD-3-Clause

#include <benchmark/benchmark.h>
#include <cmath>
#include <cstdint>
#include <stdio.h>

#include "../common.h"
#include "01_gather_aos_ispc.h"

static Docs
docs("Moments (the sums of the coordinates and of the squared norms) of point records of N = 2, 3, 4 floats\n"
"in an array of structures, read packed (record i), strided (record 2 * i) or indexed (record idx[i],\n"
"random records):\n"
"moments_gather_<way>_<N> - a varying struct access (gathers)\n"
"moments_transpose_<way>_<N> - aos_to_soa<N> of contiguous records (of records copied lane by lane for\n"
"the indexed way)\n"
"moments_wide_<way>_<N> - the coordinates read as one contiguous float array (packed and strided)\n"
"Expectation:\n"
" - No regressions\n"
" - Packed and strided gathers as fast as the transposes (coalesced into vector loads); the indexed\n"
" gathers stay genuine gathers\n");

WARM_UP_RUN();

// Number of records the gang reads.
#define ARGS Arg(256)->Arg(4096)->Arg(16384)

static uint64_t next_random(uint64_t &s) {
s ^= s << 13;
s ^= s >> 7;
s ^= s << 17;
return s;
}

static void init(float *f, int floats, int *idx, int n) {
uint64_t s = 0x9E3779B97F4A7C15ull;
for (int i = 0; i < floats; i++) {
f[i] = static_cast<float>(next_random(s) % 2000) * 0.001f - 1.0f;
}
// Random records, with repeats and omissions, so that a kernel that ignores idx gets other moments.
for (int i = 0; i < n; i++) {
idx[i] = static_cast<int>(next_random(s) % static_cast<uint64_t>(n));
}
}

// Compares out with the moments of the records rec(0 .. n - 1) of f.
template <typename Rec> static bool check(const float *f, int n, int N, const float *out, Rec rec) {
double s[4] = {0, 0, 0, 0}, q = 0;
for (int i = 0; i < n; i++) {
for (int k = 0; k < N; k++) {
double v = f[rec(i) * N + k];
s[k] += v;
q += v * v;
}
}
for (int k = 0; k <= N; k++) {
double want = k < N ? s[k] : q;
if (!(std::abs(out[k] - want) <= 1e-3 * (1.0 + std::abs(want)))) {
printf("Error k=%d: %f, expected %f\n", k, out[k], want);
return false;
}
}
return true;
}

// WAY is packed, strided or indexed; strided reads every other record of 2 * n records.
#define BENCH(VARIANT, WAY, N) \
static void moments_##VARIANT##_##WAY##_##N(benchmark::State &state) { \
int n = static_cast<int>(state.range(0)); \
bool strided = #WAY[0] == 's', indexed = #WAY[0] == 'i'; \
int records = strided ? 2 * n : n; \
float *f = static_cast<float *>(aligned_alloc_helper(sizeof(float) * N * records)); \
int *idx = static_cast<int *>(aligned_alloc_helper(sizeof(int) * n)); \
float out[N + 1]; \
for (int k = 0; k <= N; k++) { \
out[k] = NAN; \
} \
init(f, N * records, idx, n); \
auto *pts = reinterpret_cast<ispc::point##N *>(f); \
for (auto _ : state) { \
CALL_##WAY(VARIANT, N); \
} \
bool ok = check(f, n, N, out, [&](int i) { return strided ? 2 * i : indexed ? idx[i] : i; }); \
if (!ok) \
fail_benchmark(state); \
aligned_free_helper(f); \
aligned_free_helper(idx); \
state.SetComplexityN(state.range(0)); \
} \
BENCHMARK(moments_##VARIANT##_##WAY##_##N)->ARGS;

#define CALL_packed(VARIANT, N) ispc::moments_##VARIANT##_packed_##N(pts, n, out)
#define CALL_strided(VARIANT, N) ispc::moments_##VARIANT##_strided_##N(pts, n, out)
#define CALL_indexed(VARIANT, N) ispc::moments_##VARIANT##_indexed_##N(pts, idx, n, out)

#define BENCH_N(N) \
BENCH(gather, packed, N) \
BENCH(transpose, packed, N) \
BENCH(wide, packed, N) \
BENCH(gather, strided, N) \
BENCH(transpose, strided, N) \
BENCH(wide, strided, N) \
BENCH(gather, indexed, N) \
BENCH(transpose, indexed, N)

BENCH_N(2)
BENCH_N(3)
BENCH_N(4)

CHECKED_BENCHMARK_MAIN();
Loading
Loading