[CCCL] Add missing CCCL components: c2h, nvbench_helper, cmake, cudax, AGENTS.md

Added 863 files from NVIDIA/cccl sparse checkout:
- c2h/ (27 files): Catch2 test helpers — generators, validators, runner
- nvbench_helper/ (10 files): Benchmark harness utilities
- cmake/ (29 files): CMake presets and build helpers
- cudax/ (794 files): Experimental CUDA extensions
- AGENTS.md: NVIDIA's official AI agent instructions for CCCL
- CMakePresets.json: Standardized build configurations
- cccl-version.json: Version tracking

Also added CCCL_ASSET_MAP.md mapping all 4295 CCCL files to
competition value and PRD items.

cccl_upstream now covers 100% of competition-critical assets:
- 27 tuning headers (SM80/90/100 benchmark data)
- 32 dispatch headers (algorithm implementations)
- 60 Thrust examples (correctness verification)
- 217 CUB Catch2 tests (regression matrix)
- 153 CUB benchmarks (parameter space search)
- 18 CUB examples (API verification)
- 27 test helpers + benchmark harness
- 794 cudax experimental extensions
This commit is contained in:
muh-bot
2026-08-06 02:14:18 +00:00
parent b0d597363a
commit dedf08166a
864 changed files with 174321 additions and 0 deletions

View File

@@ -0,0 +1,89 @@
cmake_minimum_required(VERSION 3.21)
project(nvbench_helper LANGUAGES CXX CUDA)
include(CMakeDependentOption)
cccl_get_cub()
cccl_get_cudatoolkit()
cccl_get_libcudacxx()
cccl_get_nvbench()
cccl_get_thrust()
add_library(cccl.nvbench_helper OBJECT nvbench_helper/nvbench_helper.cu)
cccl_configure_target(cccl.nvbench_helper)
target_link_libraries(
cccl.nvbench_helper
PUBLIC
libcudacxx::libcudacxx
CUB::CUB
# This Thrust target does not define any host/device system. Those are added later if needed:
Thrust::Thrust
nvbench::nvbench
PRIVATE #
cccl.compiler_interface
CUDA::curand
)
target_include_directories(
cccl.nvbench_helper
PUBLIC "${CMAKE_CURRENT_LIST_DIR}/nvbench_helper"
)
# Old NVCC incorrectly infers the host device annotation of std::map::map() which calls a non-host device member
if (CUDAToolkit_VERSION VERSION_LESS "12.5.0")
target_compile_options(cccl.nvbench_helper PUBLIC -diag-suppress 20011)
endif()
if (CCCL_ENABLE_TILE)
target_compile_options(
cccl.nvbench_helper
PUBLIC "$<$<COMPILE_LANG_AND_ID:CUDA,NVIDIA>:--enable-tile>"
)
endif()
# Don't enable tests when pulled in as an internal dependency.
# Require CCCL_ENABLE_NVBENCH_HELPER to be explicitly enabled.
cmake_dependent_option(
nvbench_helper_ENABLE_TESTING
"Enable tests for nvbench_helper"
ON
CCCL_ENABLE_NVBENCH_HELPER
OFF
)
mark_as_advanced(nvbench_helper_ENABLE_TESTING)
if (nvbench_helper_ENABLE_TESTING)
cccl_get_catch2()
cccl_get_boost()
thrust_create_target(cccl.nvbench_helper.test.Thrust.cpp.cuda DEVICE CUDA)
thrust_create_target(cccl.nvbench_helper.test.Thrust.cpp.cpp DEVICE CPP)
function(add_nvbench_helper_test device_system)
set(nvbench_helper_test_target nvbench_helper.test.${device_system})
cccl_add_executable(
${nvbench_helper_test_target}
ADD_CTEST
NO_METATARGETS
SOURCES
test/gen_seed.cu
test/gen_range.cu
test/gen_entropy.cu
test/gen_uniform_distribution.cu
test/gen_power_law_distribution.cu
)
target_link_libraries(
${nvbench_helper_test_target}
PRIVATE
cccl.compiler_interface
cccl.nvbench_helper
cccl.nvbench_helper.test.Thrust.cpp.${device_system}
Catch2::Catch2WithMain
Boost::math
)
endfunction()
add_nvbench_helper_test(cpp)
add_nvbench_helper_test(cuda)
endif()

View File

@@ -0,0 +1,124 @@
// SPDX-FileCopyrightText: Copyright (c) 2025, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
#pragma once
#include <cuda/cmath>
#include <cuda/ptx>
#include <cuda/std/cstdint>
#include <cuda/std/cstring>
#include <cuda/std/type_traits>
#include <cuda/utility>
template <typename T>
__device__ __forceinline__ static T generate_random_data()
{
constexpr auto size = static_cast<int>(cuda::ceil_div(sizeof(T), sizeof(uint32_t)));
uint32_t data[size];
for (int i = 0; i < size; i++)
{
data[i] = cuda::ptx::get_sreg_clock();
}
T ret;
::cuda::std::memcpy(&ret, data, sizeof(T));
return ret;
}
// When benchmarking algorithms sensitive to data distribution, each thread should use a different seed to get different
// data.
template <typename T>
__device__ __forceinline__ static T generate_random_data(uint32_t& seed)
{
constexpr auto size = static_cast<int>(cuda::ceil_div(sizeof(T), sizeof(uint32_t)));
uint32_t data[size];
for (int i = 0; i < size; i++)
{
// https://en.wikipedia.org/wiki/Linear_congruential_generator
seed = 1664525 * seed + 1013904223;
data[i] = seed;
}
T ret;
cuda::std::memcpy(&ret, data, sizeof(T));
return ret;
}
__device__ static int device_var[16];
template <typename T>
__device__ __forceinline__ static void sink(T value)
{
if (cuda::ptx::get_sreg_smid() == static_cast<uint32_t>(-1))
{
*reinterpret_cast<T*>(device_var) = value;
}
}
template <typename T, int Size>
__device__ __forceinline__ static void sink(T (&values)[Size])
{
if (cuda::ptx::get_sreg_smid() == static_cast<uint32_t>(-1))
{
// use float instead of T to ensure summation order matters (due to floating-point non-associativity), making
// `action` less likely to be optimized away
float sum(0.0f);
for (int i = 0; i < Size; ++i)
{
sum += values[i];
}
*reinterpret_cast<float*>(device_var) += sum;
}
}
template <int ThreadsPerBlock, int UnrollFactor, typename ActionT, typename T>
__launch_bounds__(ThreadsPerBlock) __global__ static void benchmark_kernel(const ActionT action)
{
auto data = generate_random_data<T>();
cuda::static_for<UnrollFactor>([&]([[maybe_unused]] auto _) {
data = action(data);
});
sink(data);
}
// This variant uses pragma directive to prevent loop unrolling, which can cause high register pressure and skew
// benchmark results.
// For keys-only benchmarks, set ValueT to void.
template <int ItemsPerThread, typename KeyT, typename ValueT, typename ActionT, typename... Args>
__global__ static void benchmark_kernel(int num_iterations, const ActionT action, Args... args)
{
constexpr int warp_threads = 32;
constexpr bool has_values = !cuda::std::is_void_v<ValueT>;
KeyT keys[ItemsPerThread];
// when ValueT=void, declare values as char array
[[maybe_unused]] cuda::std::conditional_t<has_values, ValueT, char> values[ItemsPerThread];
const auto tid = threadIdx.x;
// Shift tid by 7 to reduce the likelihood of threads within a warp getting monotonically increasing data
uint32_t seed = cuda::ptx::get_sreg_clock() + (tid + 7) % warp_threads;
#pragma unroll 1
for (int iter = 0; iter < num_iterations; ++iter)
{
for (int i = 0; i < ItemsPerThread; ++i)
{
keys[i] = generate_random_data<KeyT>(seed);
if constexpr (has_values)
{
values[i] = generate_random_data<ValueT>(seed);
}
}
if constexpr (has_values)
{
action(keys, values, args...);
}
else
{
action(keys, args...);
}
sink(keys);
if constexpr (has_values)
{
sink(values);
}
}
}

View File

@@ -0,0 +1,23 @@
// SPDX-FileCopyrightText: Copyright (c) 2011-2026, NVIDIA CORPORATION. All rights reserved.
// SPDX-License-Identifier: BSD-3
#pragma once
#if !TUNE_BASE
# include <cub/detail/delay_constructor.cuh>
# include <nvbench_helper.cuh>
# if !defined(TUNE_MAGIC_NS) || !defined(TUNE_L2_WRITE_LATENCY_NS) || !defined(TUNE_DELAY_CONSTRUCTOR_ID)
# error "TUNE_MAGIC_NS, TUNE_L2_WRITE_LATENCY_NS, and TUNE_DELAY_CONSTRUCTOR_ID must be defined"
# endif
using delay_constructor_t =
cub::detail::delay_constructor_t<static_cast<cub::LookbackDelayAlgorithm>(TUNE_DELAY_CONSTRUCTOR_ID),
TUNE_MAGIC_NS,
TUNE_L2_WRITE_LATENCY_NS>;
inline constexpr auto lookback_delay_policy = cub::LookbackDelayPolicy{
static_cast<cub::LookbackDelayAlgorithm>(TUNE_DELAY_CONSTRUCTOR_ID), TUNE_MAGIC_NS, TUNE_L2_WRITE_LATENCY_NS};
#endif // !TUNE_BASE

View File

@@ -0,0 +1,840 @@
#include <cub/device/device_copy.cuh>
#include <thrust/binary_search.h>
#include <thrust/count.h>
#include <thrust/detail/raw_pointer_cast.h>
#include <thrust/distance.h>
#include <thrust/execution_policy.h>
#include <thrust/fill.h>
#include <thrust/for_each.h>
#include <thrust/host_vector.h>
#include <thrust/iterator/counting_iterator.h>
#include <thrust/iterator/transform_output_iterator.h>
#include <thrust/iterator/zip_iterator.h>
#include <thrust/scan.h>
#include <thrust/tabulate.h>
#include <cuda/functional>
#include <cuda/iterator>
#include <cuda/std/__floating_point/cuda_fp_types.h> // __half, __nv_bfloat16
#include <cuda/std/bit>
#include <cuda/type_traits>
#include <cstdint>
#include <random>
#include <type_traits>
#include <curand.h>
#include <nvbench_helper.cuh>
#include "thrust/device_vector.h"
namespace detail
{
constexpr double lognormal_mean = 3.0;
constexpr double lognormal_sigma = 1.2;
enum class executor
{
host,
device
};
class host_generator_t
{
public:
template <typename T>
void generate(seed_t seed, cuda::std::span<T> device_span, bit_entropy entropy, T min, T max);
const double* new_uniform_distribution(seed_t seed, std::size_t num_items);
const double* new_lognormal_distribution(seed_t seed, std::size_t num_items);
const double* new_constant(std::size_t num_items, double val);
private:
thrust::host_vector<double> m_distribution;
};
const double* host_generator_t::new_uniform_distribution(seed_t seed, std::size_t num_items)
{
m_distribution.resize(num_items);
double* h_distribution = thrust::raw_pointer_cast(m_distribution.data());
std::default_random_engine re(seed.get());
std::uniform_real_distribution<double> dist(0.0, 1.0);
for (std::size_t i = 0; i < num_items; i++)
{
h_distribution[i] = dist(re);
}
return h_distribution;
}
const double* host_generator_t::new_lognormal_distribution(seed_t seed, std::size_t num_items)
{
m_distribution.resize(num_items);
double* h_distribution = thrust::raw_pointer_cast(m_distribution.data());
std::default_random_engine re(seed.get());
std::lognormal_distribution<double> dist(lognormal_mean, lognormal_sigma);
for (std::size_t i = 0; i < num_items; i++)
{
h_distribution[i] = dist(re);
}
return h_distribution;
}
const double* host_generator_t::new_constant(std::size_t num_items, double val)
{
m_distribution.resize(num_items);
double* h_distribution = thrust::raw_pointer_cast(m_distribution.data());
thrust::fill_n(thrust::host, h_distribution, num_items, val);
return h_distribution;
}
class device_generator_t
{
public:
device_generator_t()
{
curandCreateGenerator(&m_gen, CURAND_RNG_PSEUDO_DEFAULT);
}
~device_generator_t()
{
curandDestroyGenerator(m_gen);
}
template <typename T>
void generate(seed_t seed, cuda::std::span<T> device_span, bit_entropy entropy, T min, T max);
const double* new_uniform_distribution(seed_t seed, std::size_t num_items);
const double* new_lognormal_distribution(seed_t seed, std::size_t num_items);
const double* new_constant(std::size_t num_items, double val);
private:
curandGenerator_t m_gen;
thrust::device_vector<double> m_distribution;
};
template <typename T>
struct random_to_item_t
{
double m_min;
double m_max;
__host__ __device__ random_to_item_t(T min, T max)
: m_min(static_cast<double>(min))
, m_max(static_cast<double>(max))
{}
__host__ __device__ T operator()(double random_value) const
{
if constexpr (::cuda::is_floating_point_v<T>)
{
return static_cast<T>((m_max - m_min) * random_value + m_min);
}
else
{
return static_cast<T>(floor((m_max - m_min + 1) * random_value + m_min));
}
}
};
const double* device_generator_t::new_uniform_distribution(seed_t seed, std::size_t num_items)
{
m_distribution.resize(num_items);
double* d_distribution = thrust::raw_pointer_cast(m_distribution.data());
curandSetPseudoRandomGeneratorSeed(m_gen, seed.get());
curandGenerateUniformDouble(m_gen, d_distribution, num_items);
return d_distribution;
}
const double* device_generator_t::new_lognormal_distribution(seed_t seed, std::size_t num_items)
{
m_distribution.resize(num_items);
double* d_distribution = thrust::raw_pointer_cast(m_distribution.data());
curandSetPseudoRandomGeneratorSeed(m_gen, seed.get());
curandGenerateLogNormalDouble(m_gen, d_distribution, num_items, lognormal_mean, lognormal_sigma);
return d_distribution;
}
const double* device_generator_t::new_constant(std::size_t num_items, double val)
{
m_distribution.resize(num_items);
double* d_distribution = thrust::raw_pointer_cast(m_distribution.data());
thrust::fill_n(thrust::device, d_distribution, num_items, val);
return d_distribution;
}
struct and_t
{
template <class T>
__host__ __device__ T operator()(T a, T b) const
{
return a & b;
}
__host__ __device__ float operator()(float a, float b) const
{
const std::uint32_t result = cuda::std::bit_cast<std::uint32_t>(a) & cuda::std::bit_cast<std::uint32_t>(b);
return cuda::std::bit_cast<float>(result);
}
__host__ __device__ double operator()(double a, double b) const
{
const std::uint64_t result = cuda::std::bit_cast<std::uint64_t>(a) & cuda::std::bit_cast<std::uint64_t>(b);
return cuda::std::bit_cast<double>(result);
}
#if _CCCL_HAS_NVFP16() && _CCCL_CTK_AT_LEAST(12, 2)
__host__ __device__ __half operator()(__half a, __half b) const
{
const std::uint16_t result = cuda::std::bit_cast<std::uint16_t>(a) & cuda::std::bit_cast<std::uint16_t>(b);
return cuda::std::bit_cast<__half>(result);
}
#endif // _CCCL_HAS_NVFP16() && _CCCL_CTK_AT_LEAST(12, 2)
#if _CCCL_HAS_NVBF16() && _CCCL_CTK_AT_LEAST(12, 2)
__host__ __device__ __nv_bfloat16 operator()(__nv_bfloat16 a, __nv_bfloat16 b) const
{
const std::uint16_t result = cuda::std::bit_cast<std::uint16_t>(a) & cuda::std::bit_cast<std::uint16_t>(b);
return cuda::std::bit_cast<__nv_bfloat16>(result);
}
#endif // _CCCL_HAS_NVBF16() && _CCCL_CTK_AT_LEAST(12, 2)
template <typename T>
__host__ __device__ cuda::std::complex<T> operator()(cuda::std::complex<T> a, cuda::std::complex<T> b) const
{
const T a_real = a.real();
const T a_imag = a.imag();
const T b_real = b.real();
const T b_imag = b.imag();
using uint_t = std::conditional_t<sizeof(T) == 4, std::uint32_t, std::uint64_t>;
const auto result_real = cuda::std::bit_cast<uint_t>(a_real) & cuda::std::bit_cast<uint_t>(b_real);
const auto result_imag = cuda::std::bit_cast<uint_t>(a_imag) & cuda::std::bit_cast<uint_t>(b_imag);
return {cuda::std::bit_cast<T>(result_real), cuda::std::bit_cast<T>(result_imag)};
}
};
template <typename T>
struct set_real_t
{
cuda::std::complex<T> m_min{};
cuda::std::complex<T> m_max{};
cuda::std::complex<T>* m_d_in{};
const double* m_d_tmp{};
__host__ __device__ void operator()(std::size_t i) const
{
m_d_in[i].real(random_to_item_t<double>{m_min.real(), m_max.real()}(m_d_tmp[i]));
}
};
template <typename T>
struct set_imag_t
{
cuda::std::complex<T> m_min{};
cuda::std::complex<T> m_max{};
cuda::std::complex<T>* m_d_in{};
const double* m_d_tmp{};
__host__ __device__ void operator()(std::size_t i) const
{
m_d_in[i].imag(random_to_item_t<double>{m_min.imag(), m_max.imag()}(m_d_tmp[i]));
}
};
template <class T>
struct lognormal_transformer_t
{
std::size_t total_elements;
double sum;
__host__ __device__ T operator()(double val) const
{
return floor(val * total_elements / sum);
}
};
class generator_t
{
public:
template <typename T>
void generate(executor exec, seed_t seed, cuda::std::span<T> span, bit_entropy entropy, T min, T max)
{
construct_guard(exec);
if (exec == executor::device)
{
this->generate(thrust::device, *m_device_generator, seed, span, entropy, min, max);
}
else
{
this->generate(thrust::host, *m_host_generator, seed, span, entropy, min, max);
}
}
template <typename T>
void power_law_segment_offsets(executor exec, seed_t seed, cuda::std::span<T> span, std::size_t total_elements)
{
construct_guard(exec);
if (exec == executor::device)
{
this->power_law_segment_offsets(thrust::device, *m_device_generator, seed, span, total_elements);
}
else
{
this->power_law_segment_offsets(thrust::host, *m_host_generator, seed, span, total_elements);
}
}
private:
void construct_guard(executor exec)
{
if (exec == executor::device)
{
if (!m_device_generator)
{
m_device_generator.emplace();
}
}
else
{
if (!m_host_generator)
{
m_host_generator.emplace();
}
}
}
template <typename ExecT, typename DistT, typename T>
void generate(const ExecT& exec, DistT& dist, seed_t seed, cuda::std::span<T> span, bit_entropy entropy, T min, T max);
template <typename ExecT, typename DistT, typename T>
void generate(const ExecT& exec,
DistT& dist,
seed_t seed,
cuda::std::span<cuda::std::complex<T>> span,
bit_entropy entropy,
cuda::std::complex<T> min,
cuda::std::complex<T> max);
template <typename ExecT, typename DistT>
void generate(
const ExecT& exec, DistT& dist, seed_t seed, cuda::std::span<bool> span, bit_entropy entropy, bool min, bool max);
template <typename ExecT, typename DistT, typename T>
void power_law_segment_offsets(
const ExecT& exec, DistT& dist, seed_t seed, cuda::std::span<T> span, std::size_t total_elements);
std::optional<host_generator_t> m_host_generator;
std::optional<device_generator_t> m_device_generator;
};
template <typename ExecT, typename DistT, typename T>
void generator_t::generate(
const ExecT& exec, DistT& dist, seed_t seed, cuda::std::span<T> span, bit_entropy entropy, T min, T max)
{
switch (entropy)
{
case bit_entropy::_1_000: {
const double* uniform_distribution = dist.new_uniform_distribution(seed, span.size());
thrust::transform(
exec,
uniform_distribution,
uniform_distribution + span.size(),
span.data(),
::cuda::proclaim_copyable_arguments(random_to_item_t<T>(min, max)));
return;
}
case bit_entropy::_0_000: {
std::mt19937 rng;
rng.seed(static_cast<std::mt19937::result_type>(seed.get()));
std::uniform_real_distribution<float> dist(0.0f, 1.0f);
T random_value = random_to_item_t<T>(min, max)(dist(rng));
thrust::fill(exec, span.data(), span.data() + span.size(), random_value);
return;
}
default: {
const double* uniform_distribution = dist.new_uniform_distribution(seed, span.size());
++seed;
thrust::transform(
exec,
uniform_distribution,
uniform_distribution + span.size(),
span.data(),
::cuda::proclaim_copyable_arguments(random_to_item_t<T>(min, max)));
const int number_of_steps = static_cast<int>(entropy);
constexpr bool is_device = std::is_same_v<DistT, device_generator_t>;
using vec_t = std::conditional_t<is_device, thrust::device_vector<T>, thrust::host_vector<T>>;
vec_t tmp_vec(span.size());
cuda::std::span<T> tmp(thrust::raw_pointer_cast(tmp_vec.data()), tmp_vec.size());
for (int i = 0; i < number_of_steps; i++, ++seed)
{
this->generate(is_device ? executor::device : executor::host, seed, tmp, bit_entropy::_1_000, min, max);
thrust::transform(
exec,
span.data(),
span.data() + span.size(),
tmp.data(),
span.data(),
::cuda::proclaim_copyable_arguments(and_t{}));
}
return;
}
};
}
template <typename ExecT, typename DistT, typename T>
void generator_t::generate(
const ExecT& exec,
DistT& dist,
seed_t seed,
cuda::std::span<cuda::std::complex<T>> span,
bit_entropy entropy,
cuda::std::complex<T> min,
cuda::std::complex<T> max)
{
switch (entropy)
{
case bit_entropy::_1_000: {
const double* uniform_distribution = dist.new_uniform_distribution(seed, span.size());
thrust::for_each_n(exec,
thrust::make_counting_iterator(std::size_t{0}),
span.size(),
set_real_t<T>{min, max, span.data(), uniform_distribution});
++seed;
uniform_distribution = dist.new_uniform_distribution(seed, span.size());
thrust::for_each_n(exec,
thrust::make_counting_iterator(std::size_t{0}),
span.size(),
set_imag_t<T>{min, max, span.data(), uniform_distribution});
++seed;
return;
}
case bit_entropy::_0_000: {
std::mt19937 rng;
rng.seed(static_cast<std::mt19937::result_type>(seed.get()));
std::uniform_real_distribution<double> dist(0.0f, 1.0f);
const float random_imag = random_to_item_t<double>(min.imag(), max.imag())(dist(rng));
const float random_real = random_to_item_t<double>(min.imag(), max.imag())(dist(rng));
thrust::fill(exec, span.data(), span.data() + span.size(), cuda::std::complex<T>{random_real, random_imag});
return;
}
default: {
const double* uniform_distribution = dist.new_uniform_distribution(seed, span.size());
thrust::for_each_n(exec,
thrust::make_counting_iterator(std::size_t{0}),
span.size(),
set_real_t<T>{min, max, span.data(), uniform_distribution});
++seed;
uniform_distribution = dist.new_uniform_distribution(seed, span.size());
thrust::for_each_n(exec,
thrust::make_counting_iterator(std::size_t{0}),
span.size(),
set_imag_t<T>{min, max, span.data(), uniform_distribution});
++seed;
const int number_of_steps = static_cast<int>(entropy);
constexpr bool is_device = std::is_same_v<DistT, device_generator_t>;
using vec_t = std::conditional_t<is_device,
thrust::device_vector<cuda::std::complex<T>>,
thrust::host_vector<cuda::std::complex<T>>>;
vec_t tmp_vec(span.size());
cuda::std::span<cuda::std::complex<T>> tmp(thrust::raw_pointer_cast(tmp_vec.data()), tmp_vec.size());
for (int i = 0; i < number_of_steps; i++, ++seed)
{
this->generate(is_device ? executor::device : executor::host, seed, tmp, bit_entropy::_1_000, min, max);
thrust::transform(
exec,
span.data(),
span.data() + span.size(),
tmp.data(),
span.data(),
::cuda::proclaim_copyable_arguments(and_t{}));
}
return;
}
};
}
struct random_to_probability_t
{
double m_probability;
__host__ __device__ bool operator()(double random_value) const
{
return random_value < m_probability;
}
};
template <typename ExecT, typename DistT>
void generator_t::generate(
const ExecT& exec,
DistT& dist,
seed_t seed,
cuda::std::span<bool> span,
bit_entropy entropy,
bool /* min */,
bool /* max */)
{
if (entropy == bit_entropy::_0_000)
{
thrust::fill(exec, span.data(), span.data() + span.size(), false);
}
else if (entropy == bit_entropy::_1_000)
{
thrust::fill(exec, span.data(), span.data() + span.size(), true);
}
else
{
const double* uniform_distribution = dist.new_uniform_distribution(seed, span.size());
thrust::transform(
exec,
uniform_distribution,
uniform_distribution + span.size(),
span.data(),
::cuda::proclaim_copyable_arguments(random_to_probability_t{entropy_to_probability(entropy)}));
}
}
template <class T>
struct lognormal_adjust_t
{
T* segment_sizes{};
__host__ __device__ T operator()(std::size_t sid) const
{
return segment_sizes[sid] + 1;
}
};
template <typename ExecT, typename DistT, typename T>
void generator_t::power_law_segment_offsets(
const ExecT& exec, DistT& dist, seed_t seed, cuda::std::span<T> device_segment_offsets, std::size_t total_elements)
{
const std::size_t total_segments = device_segment_offsets.size() - 1;
const double* uniform_distribution = dist.new_lognormal_distribution(seed, total_segments);
if (static_cast<std::size_t>(thrust::count(exec, uniform_distribution, uniform_distribution + total_segments, 0.0))
== total_segments)
{
uniform_distribution = dist.new_constant(total_segments, 1.0);
}
const double sum = thrust::reduce(exec, uniform_distribution, uniform_distribution + total_segments);
thrust::transform(
exec,
uniform_distribution,
uniform_distribution + total_segments,
device_segment_offsets.data(),
::cuda::proclaim_copyable_arguments(lognormal_transformer_t<T>{total_elements, sum}));
const int diff =
total_elements
- thrust::reduce(exec, device_segment_offsets.data(), device_segment_offsets.data() + device_segment_offsets.size());
if (diff > 0)
{
thrust::tabulate(exec,
device_segment_offsets.data(),
device_segment_offsets.data() + diff,
lognormal_adjust_t<T>{device_segment_offsets.data()});
}
thrust::exclusive_scan(
exec,
device_segment_offsets.data(),
device_segment_offsets.data() + device_segment_offsets.size(),
device_segment_offsets.data());
}
template <typename T>
void gen(executor exec, seed_t seed, cuda::std::span<T> span, bit_entropy entropy, T min, T max)
{
generator_t{}.generate(exec, seed, span, entropy, min, max);
}
template <typename T>
void gen_host(seed_t seed, cuda::std::span<T> span, bit_entropy entropy, T min, T max)
{
gen(executor::host, seed, span, entropy, min, max);
}
template <typename T>
void gen_device(seed_t seed, cuda::std::span<T> device_span, bit_entropy entropy, T min, T max)
{
gen(executor::device, seed, device_span, entropy, min, max);
}
template <class T>
struct offset_to_iterator_t
{
T* base_it;
__host__ __device__ __forceinline__ T* operator()(std::size_t offset) const
{
return base_it + offset;
}
};
template <class T>
struct repeat_index_t
{
__host__ __device__ __forceinline__ cuda::constant_iterator<T> operator()(std::size_t i)
{
return cuda::constant_iterator<T>(static_cast<T>(i));
}
};
struct offset_to_size_t
{
std::size_t* offsets = nullptr;
__host__ __device__ __forceinline__ std::size_t operator()(std::size_t i)
{
return offsets[i + 1] - offsets[i];
}
};
template <typename T>
void gen_key_segments(executor exec, seed_t, cuda::std::span<T> keys, cuda::std::span<std::size_t> segment_offsets)
{
thrust::counting_iterator<int> iota(0);
offset_to_iterator_t<T> dst_transform_op{keys.data()};
const std::size_t total_segments = segment_offsets.size() - 1;
auto d_range_srcs = thrust::make_transform_iterator(iota, repeat_index_t<T>{});
auto d_range_dsts = thrust::make_transform_iterator(segment_offsets.data(), dst_transform_op);
auto d_range_sizes = thrust::make_transform_iterator(iota, offset_to_size_t{segment_offsets.data()});
if (exec == executor::device)
{
std::uint8_t* d_temp_storage = nullptr;
std::size_t temp_storage_bytes = 0;
cub::DeviceCopy::Batched(
d_temp_storage, temp_storage_bytes, d_range_srcs, d_range_dsts, d_range_sizes, total_segments);
thrust::device_vector<std::uint8_t> temp_storage(temp_storage_bytes);
d_temp_storage = thrust::raw_pointer_cast(temp_storage.data());
cub::DeviceCopy::Batched(
d_temp_storage, temp_storage_bytes, d_range_srcs, d_range_dsts, d_range_sizes, total_segments);
cudaDeviceSynchronize();
}
else
{
for (std::size_t sid = 0; sid < total_segments; sid++)
{
thrust::copy(d_range_srcs[sid], d_range_srcs[sid] + d_range_sizes[sid], d_range_dsts[sid]);
}
}
}
template <class T>
struct ge_t
{
T val;
__host__ __device__ bool operator()(T x)
{
return x >= val;
}
};
template <typename T>
std::size_t gen_uniform_offsets(
executor exec,
seed_t seed,
cuda::std::span<T> segment_offsets,
std::size_t min_segment_size,
std::size_t max_segment_size)
{
const T total_elements = segment_offsets.size() - 2;
gen(exec,
seed,
segment_offsets,
bit_entropy::_1_000,
static_cast<T>(min_segment_size),
static_cast<T>(max_segment_size));
auto tail = [&](const auto& policy) {
thrust::fill_n(policy, segment_offsets.data() + total_elements, 1, total_elements + 1);
thrust::exclusive_scan(
policy, segment_offsets.data(), segment_offsets.data() + segment_offsets.size(), segment_offsets.data());
auto iter = thrust::find_if(
policy, segment_offsets.data(), segment_offsets.data() + segment_offsets.size(), ge_t<T>{total_elements});
auto dist = cuda::std::distance(segment_offsets.data(), iter);
thrust::fill_n(policy, segment_offsets.data() + dist, 1, total_elements);
return dist + 1;
};
if (exec == executor::device)
{
return tail(thrust::device);
}
return tail(thrust::host);
}
/**
* @brief Generates a vector of random key segments.
*
* Not all parameter combinations can be satisfied. For instance, if the total
* elements is less than the minimal segment size, the function will return a
* vector with a single element that is outside of the requested range.
* At most one segment can be out of the requested range.
*/
template <typename T>
void gen_uniform_key_segments_host(
seed_t seed, cuda::std::span<T> keys, std::size_t min_segment_size, std::size_t max_segment_size)
{
thrust::host_vector<std::size_t> segment_offsets(keys.size() + 2);
{
cuda::std::span<std::size_t> segment_offsets_span(
thrust::raw_pointer_cast(segment_offsets.data()), segment_offsets.size());
const std::size_t offsets_size =
gen_uniform_offsets(executor::host, seed, segment_offsets_span, min_segment_size, max_segment_size);
segment_offsets.resize(offsets_size);
}
cuda::std::span<std::size_t> segment_offsets_span(
thrust::raw_pointer_cast(segment_offsets.data()), segment_offsets.size());
gen_key_segments(executor::host, seed, keys, segment_offsets_span);
}
template <typename T>
void gen_uniform_key_segments_device(
seed_t seed, cuda::std::span<T> keys, std::size_t min_segment_size, std::size_t max_segment_size)
{
thrust::device_vector<std::size_t> segment_offsets(keys.size() + 2);
{
cuda::std::span<std::size_t> segment_offsets_span(
thrust::raw_pointer_cast(segment_offsets.data()), segment_offsets.size());
const std::size_t offsets_size =
gen_uniform_offsets(executor::device, seed, segment_offsets_span, min_segment_size, max_segment_size);
segment_offsets.resize(offsets_size);
}
cuda::std::span<std::size_t> segment_offsets_span(
thrust::raw_pointer_cast(segment_offsets.data()), segment_offsets.size());
gen_key_segments(executor::device, seed, keys, segment_offsets_span);
}
template <typename T>
std::size_t gen_uniform_segment_offsets_host(
seed_t seed, cuda::std::span<T> segment_offsets, std::size_t min_segment_size, std::size_t max_segment_size)
{
return gen_uniform_offsets(executor::host, seed, segment_offsets, min_segment_size, max_segment_size);
}
template <typename T>
std::size_t gen_uniform_segment_offsets_device(
seed_t seed, cuda::std::span<T> segment_offsets, std::size_t min_segment_size, std::size_t max_segment_size)
{
return gen_uniform_offsets(executor::device, seed, segment_offsets, min_segment_size, max_segment_size);
}
template <typename T>
void gen_power_law_segment_offsets_host(seed_t seed, cuda::std::span<T> segment_offsets, std::size_t elements)
{
generator_t{}.power_law_segment_offsets<T>(executor::host, seed, segment_offsets, elements);
}
template <typename T>
void gen_power_law_segment_offsets_device(seed_t seed, cuda::std::span<T> segment_offsets, std::size_t elements)
{
generator_t{}.power_law_segment_offsets<T>(executor::device, seed, segment_offsets, elements);
}
void do_not_optimize([[maybe_unused]] const void* ptr) {}
} // namespace detail
#define INSTANTIATE(TYPE) \
template void detail::gen_power_law_segment_offsets_host<TYPE>(seed_t, cuda::std::span<TYPE>, std::size_t); \
template void detail::gen_power_law_segment_offsets_device<TYPE>(seed_t, cuda::std::span<TYPE>, std::size_t); \
template std::size_t detail::gen_uniform_segment_offsets_host<TYPE>( \
seed_t, cuda::std::span<TYPE>, std::size_t, std::size_t); \
template std::size_t detail::gen_uniform_segment_offsets_device<TYPE>( \
seed_t, cuda::std::span<TYPE>, std::size_t, std::size_t)
INSTANTIATE(int32_t);
INSTANTIATE(uint32_t);
INSTANTIATE(int64_t);
INSTANTIATE(uint64_t);
#undef INSTANTIATE
// Instantiates only the uniform data generators used by non-segmented benchmarks (e.g. Reduce/Scan/RadixSort).
#define INSTANTIATE_GEN(TYPE) \
template void detail::gen_device<TYPE>(seed_t, cuda::std::span<TYPE>, bit_entropy, TYPE min, TYPE max); \
template void detail::gen_host<TYPE>(seed_t, cuda::std::span<TYPE>, bit_entropy, TYPE min, TYPE max)
#define INSTANTIATE(TYPE) \
template void detail::gen_uniform_key_segments_host<TYPE>(seed_t, cuda::std::span<TYPE>, std::size_t, std::size_t); \
template void detail::gen_uniform_key_segments_device<TYPE>(seed_t, cuda::std::span<TYPE>, std::size_t, std::size_t); \
INSTANTIATE_GEN(TYPE)
INSTANTIATE(bool);
INSTANTIATE(uint8_t);
INSTANTIATE(uint16_t);
INSTANTIATE(uint32_t);
INSTANTIATE(uint64_t);
INSTANTIATE(int8_t);
INSTANTIATE(int16_t);
INSTANTIATE(int32_t);
INSTANTIATE(int64_t);
#if _CCCL_HAS_INT128()
INSTANTIATE(int128_t);
INSTANTIATE(uint128_t);
#endif
INSTANTIATE(float);
INSTANTIATE(double);
INSTANTIATE(complex32);
INSTANTIATE(complex64);
// Extended floating-point types: only the uniform generators are needed (no segmented-sort key generators yet).
#if _CCCL_HAS_NVFP16() && _CCCL_CTK_AT_LEAST(12, 2)
INSTANTIATE_GEN(__half);
#endif
#if _CCCL_HAS_NVBF16() && _CCCL_CTK_AT_LEAST(12, 2)
INSTANTIATE_GEN(__nv_bfloat16);
#endif
#undef INSTANTIATE
#undef INSTANTIATE_GEN

View File

@@ -0,0 +1,777 @@
#pragma once
#include <cub/thread/thread_operators.cuh>
#include <thrust/device_vector.h>
#include <thrust/execution_policy.h>
#include <cuda/std/cmath>
#include <cuda/std/complex>
#include <cuda/std/functional>
#include <cuda/std/limits>
#include <cuda/std/span>
#include <cuda/std/type_traits>
#if THRUST_DEVICE_SYSTEM == THRUST_DEVICE_SYSTEM_CUDA
# include <cuda/memory_resource>
# include <cuda/std/execution>
# include <cuda/stream>
#endif // THRUST_DEVICE_SYSTEM == THRUST_DEVICE_SYSTEM_CUDA
#include <map>
#include <stdexcept>
#include <nvbench/nvbench.cuh>
#if _CCCL_HAS_INT128()
using int128_t = __int128_t;
using uint128_t = __uint128_t;
NVBENCH_DECLARE_TYPE_STRINGS(int128_t, "I128", "int128_t");
NVBENCH_DECLARE_TYPE_STRINGS(uint128_t, "U128", "uint128_t");
#endif
using complex32 = cuda::std::complex<float>;
using complex64 = cuda::std::complex<double>;
#if _CCCL_HAS_NVFP16()
NVBENCH_DECLARE_TYPE_STRINGS(__half, "F16", "half");
NVBENCH_DECLARE_TYPE_STRINGS(cuda::std::complex<__half>, "C16", "complex_half");
#endif
#if _CCCL_HAS_NVBF16()
NVBENCH_DECLARE_TYPE_STRINGS(__nv_bfloat16, "BF16", "bfloat16");
NVBENCH_DECLARE_TYPE_STRINGS(cuda::std::complex<__nv_bfloat16>, "CB16", "complex_bfloat16");
#endif
NVBENCH_DECLARE_TYPE_STRINGS(complex32, "C32", "complex32");
NVBENCH_DECLARE_TYPE_STRINGS(complex64, "C64", "complex64");
NVBENCH_DECLARE_TYPE_STRINGS(::cuda::std::false_type, "false", "false_type");
NVBENCH_DECLARE_TYPE_STRINGS(::cuda::std::true_type, "true", "true_type");
NVBENCH_DECLARE_TYPE_STRINGS(cub::detail::arg_min, "arg_min", "cub::detail::arg_min");
NVBENCH_DECLARE_TYPE_STRINGS(cub::detail::arg_max, "arg_max", "cub::detail::arg_max");
template <typename T, T I>
struct nvbench::type_strings<::cuda::std::integral_constant<T, I>>
{
static std::string input_string()
{
return std::to_string(I);
}
static std::string description()
{
return "integral_constant<" + type_strings<T>::description() + ", " + std::to_string(I) + ">";
}
};
namespace detail
{
template <class List, class... Ts>
struct push_back
{};
template <class... As, class... Ts>
struct push_back<nvbench::type_list<As...>, Ts...>
{
using type = nvbench::type_list<As..., Ts...>;
};
} // namespace detail
template <class List, class... Ts>
using push_back_t = typename detail::push_back<List, Ts...>::type;
#ifdef TUNE_OffsetT
using offset_types = nvbench::type_list<TUNE_OffsetT>;
#else
using offset_types = nvbench::type_list<int32_t, int64_t>;
#endif
#ifdef TUNE_T
using integral_types = nvbench::type_list<TUNE_T>;
using fundamental_types = nvbench::type_list<TUNE_T>;
using all_types = nvbench::type_list<TUNE_T>;
#else
// keep those lists in sync with the documentation in tuning_infra.rst
using integral_types = nvbench::type_list<int8_t, int16_t, int32_t, int64_t>;
using fundamental_types =
nvbench::type_list<int8_t,
int16_t,
int32_t,
int64_t,
# if _CCCL_HAS_INT128()
int128_t,
# endif
float,
double>;
using all_types =
nvbench::type_list<int8_t,
int16_t,
int32_t,
int64_t,
# if _CCCL_HAS_INT128()
int128_t,
# endif
float,
double,
complex32>;
#endif
template <class T>
class value_wrapper_t
{
T m_val{};
public:
explicit value_wrapper_t(T val)
: m_val(val)
{}
T get() const
{
return m_val;
}
value_wrapper_t& operator++()
{
m_val++;
return *this;
}
};
class seed_t : public value_wrapper_t<unsigned long long int>
{
public:
using value_wrapper_t::value_wrapper_t;
using value_wrapper_t::operator++;
seed_t()
: value_wrapper_t(42)
{}
};
enum class bit_entropy
{
_1_000 = 0,
_0_811 = 1,
_0_544 = 2,
_0_337 = 3,
_0_201 = 4,
_0_000 = 4200
};
NVBENCH_DECLARE_TYPE_STRINGS(bit_entropy, "BE", "bit entropy");
[[nodiscard]] inline double entropy_to_probability(bit_entropy entropy)
{
switch (entropy)
{
case bit_entropy::_1_000:
return 1.0;
case bit_entropy::_0_811:
return 0.811;
case bit_entropy::_0_544:
return 0.544;
case bit_entropy::_0_337:
return 0.337;
case bit_entropy::_0_201:
return 0.201;
case bit_entropy::_0_000:
[[fallthrough]];
default:
return 0.0;
}
}
[[nodiscard]] inline bit_entropy str_to_entropy(std::string str)
{
if (str == "1.000")
{
return bit_entropy::_1_000;
}
else if (str == "0.811")
{
return bit_entropy::_0_811;
}
else if (str == "0.544")
{
return bit_entropy::_0_544;
}
else if (str == "0.337")
{
return bit_entropy::_0_337;
}
else if (str == "0.201")
{
return bit_entropy::_0_201;
}
else if (str == "0.000")
{
return bit_entropy::_0_000;
}
throw std::runtime_error("Can't convert string to bit entropy");
}
// Creates an interpolated value of type T between min (at = 0.0) and max (at = 1.0).
template <typename T>
[[nodiscard]] T lerp_min_max(double at) noexcept
{
if (at == 1.0)
{
return ::cuda::std::numeric_limits<T>::max();
}
const auto min_val = static_cast<double>(::cuda::std::numeric_limits<T>::lowest());
const auto max_val = static_cast<double>(::cuda::std::numeric_limits<T>::max());
return static_cast<T>(::cuda::std::lerp(min_val, max_val, at));
}
namespace detail
{
void do_not_optimize(const void* ptr);
template <typename T>
void gen_host(seed_t seed, cuda::std::span<T> data, bit_entropy entropy, T min, T max);
template <typename T>
void gen_device(seed_t seed, cuda::std::span<T> data, bit_entropy entropy, T min, T max);
template <typename T>
void gen_uniform_key_segments_host(
seed_t seed, cuda::std::span<T> data, std::size_t min_segment_size, std::size_t max_segment_size);
template <typename T>
void gen_uniform_key_segments_device(
seed_t seed, cuda::std::span<T> data, std::size_t min_segment_size, std::size_t max_segment_size);
template <typename T>
std::size_t gen_uniform_segment_offsets_host(
seed_t seed, cuda::std::span<T> segment_offsets, std::size_t min_segment_size, std::size_t max_segment_size);
template <typename T>
std::size_t gen_uniform_segment_offsets_device(
seed_t seed, cuda::std::span<T> segment_offsets, std::size_t min_segment_size, std::size_t max_segment_size);
template <typename T>
void gen_power_law_segment_offsets_host(seed_t seed, cuda::std::span<T> segment_offsets, std::size_t elements);
template <typename T>
void gen_power_law_segment_offsets_device(seed_t seed, cuda::std::span<T> segment_offsets, std::size_t elements);
namespace
{
struct generator_base_t
{
seed_t m_seed{};
const std::size_t m_elements{0};
const bit_entropy m_entropy{bit_entropy::_1_000};
template <typename T>
thrust::device_vector<T> generate(T min, T max)
{
thrust::device_vector<T> vec(m_elements);
cuda::std::span<T> span(thrust::raw_pointer_cast(vec.data()), m_elements);
#if THRUST_DEVICE_SYSTEM == THRUST_DEVICE_SYSTEM_CUDA
gen_device(m_seed, span, m_entropy, min, max);
#else
gen_host(m_seed, span, m_entropy, min, max);
#endif
++m_seed;
return vec;
}
};
template <class T>
struct vector_generator_t : generator_base_t
{
const T m_min{::cuda::std::numeric_limits<T>::min()};
const T m_max{::cuda::std::numeric_limits<T>::max()};
operator thrust::device_vector<T>()
{
return generator_base_t::generate(m_min, m_max);
}
};
template <>
struct vector_generator_t<void> : generator_base_t
{
template <typename T>
operator thrust::device_vector<T>()
{
return generator_base_t::generate(::cuda::std::numeric_limits<T>::min(), ::cuda::std::numeric_limits<T>::max());
}
// This overload is needed because numeric limits is not specialized for complex, making
// the min and max values for complex equal zero.
template <typename T>
operator thrust::device_vector<::cuda::std::complex<T>>()
{
const auto min =
::cuda::std::complex<T>{::cuda::std::numeric_limits<T>::min(), ::cuda::std::numeric_limits<T>::min()};
const auto max =
::cuda::std::complex<T>{::cuda::std::numeric_limits<T>::max(), ::cuda::std::numeric_limits<T>::max()};
return generator_base_t::generate(min, max);
}
};
struct uniform_key_segments_generator_t
{
seed_t m_seed{};
const std::size_t m_total_elements{0};
const std::size_t m_min_segment_size{0};
const std::size_t m_max_segment_size{0};
template <class KeyT>
operator thrust::device_vector<KeyT>()
{
thrust::device_vector<KeyT> keys_vec(m_total_elements);
cuda::std::span<KeyT> keys(thrust::raw_pointer_cast(keys_vec.data()), keys_vec.size());
#if THRUST_DEVICE_SYSTEM == THRUST_DEVICE_SYSTEM_CUDA
gen_uniform_key_segments_device(m_seed, keys, m_min_segment_size, m_max_segment_size);
#else
gen_uniform_key_segments_host(m_seed, keys, m_min_segment_size, m_max_segment_size);
#endif
++m_seed;
return keys_vec;
}
};
struct uniform_segment_offsets_generator_t
{
seed_t m_seed{};
const std::size_t m_total_elements{0};
const std::size_t m_min_segment_size{0};
const std::size_t m_max_segment_size{0};
template <class OffsetT>
operator thrust::device_vector<OffsetT>()
{
thrust::device_vector<OffsetT> offsets_vec(m_total_elements + 2);
cuda::std::span<OffsetT> offsets(thrust::raw_pointer_cast(offsets_vec.data()), offsets_vec.size());
const std::size_t offsets_size =
#if THRUST_DEVICE_SYSTEM == THRUST_DEVICE_SYSTEM_CUDA
gen_uniform_segment_offsets_device(m_seed, offsets, m_min_segment_size, m_max_segment_size);
#else
gen_uniform_segment_offsets_host(m_seed, offsets, m_min_segment_size, m_max_segment_size);
#endif
offsets_vec.resize(offsets_size);
offsets_vec.shrink_to_fit();
++m_seed;
return offsets_vec;
}
};
struct power_law_segment_offsets_generator_t
{
seed_t m_seed{};
const std::size_t m_elements{0};
const std::size_t m_segments{0};
template <class OffsetT>
operator thrust::device_vector<OffsetT>()
{
thrust::device_vector<OffsetT> offsets_vec(m_segments + 1);
cuda::std::span<OffsetT> offsets(thrust::raw_pointer_cast(offsets_vec.data()), offsets_vec.size());
#if THRUST_DEVICE_SYSTEM == THRUST_DEVICE_SYSTEM_CUDA
gen_power_law_segment_offsets_device(m_seed, offsets, m_elements);
#else
gen_power_law_segment_offsets_host(m_seed, offsets, m_elements);
#endif
++m_seed;
return offsets_vec;
}
};
struct gen_uniform_key_segments_t
{
uniform_key_segments_generator_t
operator()(std::size_t total_elements, std::size_t min_segment_size, std::size_t max_segment_size) const
{
return {seed_t{}, total_elements, min_segment_size, max_segment_size};
}
};
struct gen_uniform_segment_offsets_t
{
uniform_segment_offsets_generator_t
operator()(std::size_t total_elements, std::size_t min_segment_size, std::size_t max_segment_size) const
{
return {seed_t{}, total_elements, min_segment_size, max_segment_size};
}
};
struct gen_uniform_t
{
gen_uniform_key_segments_t key_segments{};
gen_uniform_segment_offsets_t segment_offsets{};
};
struct gen_power_law_segment_offsets_t
{
power_law_segment_offsets_generator_t operator()(std::size_t elements, std::size_t segments) const
{
return {seed_t{}, elements, segments};
}
};
struct gen_power_law_t
{
gen_power_law_segment_offsets_t segment_offsets{};
};
struct gen_t
{
vector_generator_t<void> operator()(std::size_t elements, bit_entropy entropy = bit_entropy::_1_000) const
{
return {seed_t{}, elements, entropy};
}
template <class T>
vector_generator_t<T> operator()(
std::size_t elements,
bit_entropy entropy = bit_entropy::_1_000,
T min = ::cuda::std::numeric_limits<T>::min,
T max = ::cuda::std::numeric_limits<T>::max()) const
{
return {{seed_t{}, elements, entropy}, min, max};
}
gen_uniform_t uniform{};
gen_power_law_t power_law{};
};
} // namespace
} // namespace detail
inline detail::gen_t generate;
template <class T>
void do_not_optimize(const T& val)
{
detail::do_not_optimize(&val);
}
struct less_t
{
template <typename DataType>
__host__ __device__ bool operator()(const DataType& lhs, const DataType& rhs) const
{
return lhs < rhs;
}
template <typename T>
__host__ __device__ inline bool
operator()(const ::cuda::std::complex<T>& lhs, const ::cuda::std::complex<T>& rhs) const
{
double magnitude_0 = cuda::std::abs(lhs);
double magnitude_1 = cuda::std::abs(rhs);
if (cuda::std::isnan(magnitude_0) || cuda::std::isnan(magnitude_1))
{
// NaN's are always equal.
return false;
}
else if (cuda::std::isinf(magnitude_0) || cuda::std::isinf(magnitude_1))
{
// If the real or imaginary part of the complex number has a very large value
// (close to the maximum representable value for a double), it is possible that
// the magnitude computation can result in positive infinity:
// ```cpp
// const double large_number = ::cuda::std::numeric_limits<double>::max() / 2;
// std::complex<double> z(large_number, large_number);
// std::abs(z) == inf;
// ```
// Dividing both components by a constant before computing the magnitude prevents overflow.
const T scaler = 0.5;
magnitude_0 = cuda::std::abs(lhs * scaler);
magnitude_1 = cuda::std::abs(rhs * scaler);
}
const T difference = cuda::std::abs(magnitude_0 - magnitude_1);
const T threshold = ::cuda::std::numeric_limits<T>::epsilon() * 2;
if (difference < threshold)
{
// Triangles with the same magnitude are sorted by their phase angle.
const T phase_angle_0 = cuda::std::arg(lhs);
const T phase_angle_1 = cuda::std::arg(rhs);
return phase_angle_0 < phase_angle_1;
}
else
{
return magnitude_0 < magnitude_1;
}
}
};
struct max_t
{
template <typename DataType>
__host__ __device__ DataType operator()(const DataType& lhs, const DataType& rhs) const
{
less_t less{};
return less(lhs, rhs) ? rhs : lhs;
}
#if _CCCL_HAS_NVFP16() && _CCCL_CTK_AT_LEAST(12, 2)
__host__ __device__ __half operator()(__half lhs, __half rhs) const
{
return static_cast<float>(lhs) < static_cast<float>(rhs) ? rhs : lhs;
}
#endif // _CCCL_HAS_NVFP16() && _CCCL_CTK_AT_LEAST(12, 2)
#if _CCCL_HAS_NVBF16() && _CCCL_CTK_AT_LEAST(12, 2)
__host__ __device__ __nv_bfloat16 operator()(__nv_bfloat16 lhs, __nv_bfloat16 rhs) const
{
return static_cast<float>(lhs) < static_cast<float>(rhs) ? rhs : lhs;
}
#endif // _CCCL_HAS_NVBF16() && _CCCL_CTK_AT_LEAST(12, 2)
};
template <class T>
struct less_then_t
{
T m_val;
[[nodiscard]] __device__ bool operator()(const T& val) const noexcept
{
return val < m_val;
}
};
_CCCL_BEGIN_NAMESPACE_CUDA
template <typename T>
struct proclaims_copyable_arguments<less_then_t<T>> : ::cuda::std::true_type
{};
_CCCL_END_NAMESPACE_CUDA
namespace
{
struct caching_allocator_t
{
using value_type = char;
caching_allocator_t() = default;
~caching_allocator_t()
{
free_all();
}
char* allocate(std::ptrdiff_t num_bytes)
{
#if THRUST_DEVICE_SYSTEM == THRUST_DEVICE_SYSTEM_CUDA
if (first_async_stream != ::cuda::invalid_stream)
{
// there was already an async allocate
throw std::runtime_error("caching_allocator_t is not intended to be used asynchronously and synchronously at the "
"same time");
}
#endif // THRUST_DEVICE_SYSTEM == THRUST_DEVICE_SYSTEM_CUDA
value_type* result{};
auto free_block = free_blocks.find(num_bytes);
if (free_block != free_blocks.end())
{
result = free_block->second;
free_blocks.erase(free_block);
}
else
{
result = do_allocate(num_bytes);
}
allocated_blocks.insert(std::make_pair(result, num_bytes));
return result;
}
void deallocate(char* ptr, size_t, [[maybe_unused]] bool check_stream = true)
{
#if THRUST_DEVICE_SYSTEM == THRUST_DEVICE_SYSTEM_CUDA
if (check_stream && first_async_stream != ::cuda::invalid_stream)
{
// there was already an async allocate
throw std::runtime_error("caching_allocator_t is not intended to be used asynchronously and synchronously at the "
"same time");
}
#endif // THRUST_DEVICE_SYSTEM == THRUST_DEVICE_SYSTEM_CUDA
auto iter = allocated_blocks.find(ptr);
if (iter == allocated_blocks.end())
{
throw std::runtime_error("Memory was not allocated by this allocator");
}
std::ptrdiff_t num_bytes = iter->second;
allocated_blocks.erase(iter);
free_blocks.insert(std::make_pair(num_bytes, ptr));
}
#if THRUST_DEVICE_SYSTEM == THRUST_DEVICE_SYSTEM_CUDA
void* allocate_sync(size_t num_bytes, size_t)
{
return allocate(static_cast<std::ptrdiff_t>(num_bytes));
}
void deallocate_sync(void* ptr, size_t num_bytes, size_t)
{
deallocate(static_cast<char*>(ptr), num_bytes);
}
void* allocate(::cuda::stream_ref __stream, size_t num_bytes, size_t)
{
if (first_async_stream == ::cuda::invalid_stream)
{
first_async_stream = __stream;
}
else if (first_async_stream != __stream)
{
throw std::runtime_error("caching_allocator_t is not intended to be used asynchronously from multiple streams");
}
value_type* result{};
auto free_block = free_blocks.find(static_cast<std::ptrdiff_t>(num_bytes));
if (free_block != free_blocks.end())
{
result = free_block->second;
free_blocks.erase(free_block);
}
else
{
const cudaError_t status = cudaMallocAsync(&result, num_bytes, __stream.get());
if (cudaSuccess != status)
{
throw std::runtime_error(std::string("Failed to allocate device memory: ") + cudaGetErrorString(status));
}
// NOTE: We can avoid a `__stream.sync()` here because `allocate` is only ever called asynchronously with a stream
// So it is fine that the memory is only valid in stream order
}
allocated_blocks.insert(std::make_pair(result, num_bytes));
return result;
}
void deallocate(::cuda::stream_ref __stream, void* ptr, size_t num_bytes, size_t)
{
if (first_async_stream != __stream)
{
throw std::runtime_error("caching_allocator_t is not intended to be used asynchronously from multiple streams");
}
// there is no need to sync the stream here and we can just insert the allocation into the free list, because the
// next allocation can only be done from the same stream again.
deallocate(static_cast<char*>(ptr), num_bytes, false);
}
#endif // THRUST_DEVICE_SYSTEM == THRUST_DEVICE_SYSTEM_CUDA
private:
using free_blocks_type = std::multimap<std::ptrdiff_t, char*>;
using allocated_blocks_type = std::map<char*, std::ptrdiff_t>;
free_blocks_type free_blocks;
allocated_blocks_type allocated_blocks;
#if THRUST_DEVICE_SYSTEM == THRUST_DEVICE_SYSTEM_CUDA
::cuda::stream_ref first_async_stream{::cuda::invalid_stream}; // just to detect wrong usage patterns
#endif // THRUST_DEVICE_SYSTEM == THRUST_DEVICE_SYSTEM_CUDA
void free_all()
{
for (auto i : free_blocks)
{
do_deallocate(i.second);
}
for (auto i : allocated_blocks)
{
do_deallocate(i.first);
}
}
value_type* do_allocate(std::size_t num_bytes)
{
value_type* result{};
#if THRUST_DEVICE_SYSTEM == THRUST_DEVICE_SYSTEM_CUDA
const cudaError_t status = cudaMalloc(&result, num_bytes);
if (cudaSuccess != status)
{
throw std::runtime_error(std::string("Failed to allocate device memory: ") + cudaGetErrorString(status));
}
#else
result = new value_type[num_bytes];
#endif
return result;
}
void do_deallocate(value_type* ptr)
{
#if THRUST_DEVICE_SYSTEM == THRUST_DEVICE_SYSTEM_CUDA
cudaFree(ptr);
#else
delete[] ptr;
#endif
}
friend bool operator==(const caching_allocator_t& lhs, const caching_allocator_t& rhs)
{
return lhs.free_blocks == rhs.free_blocks && lhs.allocated_blocks == rhs.allocated_blocks;
}
friend bool operator!=(const caching_allocator_t& lhs, const caching_allocator_t& rhs)
{
return lhs.free_blocks == rhs.free_blocks && lhs.allocated_blocks == rhs.allocated_blocks;
}
#if THRUST_DEVICE_SYSTEM == THRUST_DEVICE_SYSTEM_CUDA
friend constexpr void get_property(const caching_allocator_t&, cuda::mr::device_accessible) noexcept {}
#endif // THRUST_DEVICE_SYSTEM == THRUST_DEVICE_SYSTEM_CUDA
};
#if THRUST_DEVICE_SYSTEM == THRUST_DEVICE_SYSTEM_CUDA
auto policy(caching_allocator_t& alloc)
{
return thrust::cuda::par(alloc);
}
auto cuda_policy(caching_allocator_t& alloc)
{
return cuda::execution::gpu.with(cuda::mr::get_memory_resource, alloc);
}
#else
auto policy(caching_allocator_t&)
{
return thrust::device;
}
#endif
#if THRUST_DEVICE_SYSTEM == THRUST_DEVICE_SYSTEM_CUDA
auto policy(caching_allocator_t& alloc, nvbench::launch& launch)
{
return thrust::cuda::par(alloc).on(launch.get_stream());
}
auto cuda_policy(caching_allocator_t& alloc, nvbench::launch& launch)
{
return cuda::execution::gpu.with(cuda::mr::get_memory_resource, alloc)
.with(cuda::get_stream, launch.get_stream().get_stream());
}
#else
auto policy(caching_allocator_t&, nvbench::launch&)
{
return thrust::device;
}
#endif
#if THRUST_DEVICE_SYSTEM == THRUST_DEVICE_SYSTEM_CUDA
// Returns an environment for benchmarking using alloc as MR, launch's stream, and any additional envs passed in.
template <typename... MoreEnvs>
auto cub_bench_env(caching_allocator_t& alloc, nvbench::launch& launch, MoreEnvs... envs)
{
return cuda::std::execution::env{
::cuda::stream_ref{launch.get_stream().get_stream()},
::cuda::std::execution::prop{cuda::mr::get_memory_resource, ::cuda::mr::resource_ref<>{alloc}},
envs...};
}
#endif // THRUST_DEVICE_SYSTEM == THRUST_DEVICE_SYSTEM_CUDA
} // namespace

View File

@@ -0,0 +1,157 @@
// SPDX-FileCopyrightText: Copyright (c) 2011-2023, NVIDIA CORPORATION. All rights reserved.
// SPDX-License-Identifier: BSD-3
#include <cub/device/device_run_length_encode.cuh>
#include <thrust/count.h>
#include <thrust/execution_policy.h>
#include <thrust/host_vector.h>
#include <thrust/sort.h>
#include <thrust/transform.h>
#include <algorithm>
#include <array>
#include <nvbench_helper.cuh>
#include <catch2/catch_template_test_macros.hpp>
#include <catch2/catch_test_macros.hpp>
template <class T>
double get_expected_entropy(bit_entropy in_entropy)
{
if (in_entropy == bit_entropy::_0_000)
{
return 0.0;
}
if (in_entropy == bit_entropy::_1_000)
{
return sizeof(T) * 8;
}
const int samples = static_cast<int>(in_entropy) + 1;
const double p1 = std::pow(0.5, samples);
const double p2 = 1 - p1;
const double entropy = (-p1 * std::log2(p1)) + (-p2 * std::log2(p2));
return sizeof(T) * 8 * entropy;
}
template <class T>
double compute_actual_entropy(thrust::device_vector<T> in)
{
const int n = static_cast<int>(in.size());
#if THRUST_DEVICE_SYSTEM == THRUST_DEVICE_SYSTEM_CUDA
thrust::device_vector<T> unique(n);
thrust::device_vector<int> counts(n);
thrust::device_vector<int> num_runs(1);
thrust::sort(in.begin(), in.end(), less_t{});
// RLE
void* d_temp_storage = nullptr;
std::size_t temp_storage_bytes = 0;
T* d_in = thrust::raw_pointer_cast(in.data());
T* d_unique_out = thrust::raw_pointer_cast(unique.data());
int* d_counts_out = thrust::raw_pointer_cast(counts.data());
int* d_num_runs_out = thrust::raw_pointer_cast(num_runs.data());
cub::DeviceRunLengthEncode::Encode(
d_temp_storage, temp_storage_bytes, d_in, d_unique_out, d_counts_out, d_num_runs_out, n);
thrust::device_vector<std::uint8_t> temp_storage(temp_storage_bytes);
d_temp_storage = thrust::raw_pointer_cast(temp_storage.data());
cub::DeviceRunLengthEncode::Encode(
d_temp_storage, temp_storage_bytes, d_in, d_unique_out, d_counts_out, d_num_runs_out, n);
thrust::host_vector<int> h_counts = counts;
thrust::host_vector<int> h_num_runs = num_runs;
#else
std::vector<T> h_in(in.begin(), in.end());
std::sort(h_in.begin(), h_in.end(), less_t{});
thrust::host_vector<int> h_counts;
T prev = h_in[0];
int length = 1;
for (std::size_t i = 1; i < h_in.size(); i++)
{
const T next = h_in[i];
if (next == prev)
{
length++;
}
else
{
h_counts.push_back(length);
prev = next;
length = 1;
}
}
h_counts.push_back(length);
thrust::host_vector<int> h_num_runs(1, h_counts.size());
#endif
// normalize counts
thrust::host_vector<double> ps(h_num_runs[0]);
for (std::size_t i = 0; i < ps.size(); i++)
{
ps[i] = static_cast<double>(h_counts[i]) / n;
}
double entropy = 0.0;
if (ps.size())
{
for (double p : ps)
{
entropy -= p * std::log2(p);
}
}
return entropy;
}
TEMPLATE_LIST_TEST_CASE("Generators produce data with given entropy", "[gen]", fundamental_types)
{
constexpr int num_entropy_levels = 6;
std::array<bit_entropy, num_entropy_levels> entropy_levels{
bit_entropy::_0_000,
bit_entropy::_0_201,
bit_entropy::_0_337,
bit_entropy::_0_544,
bit_entropy::_0_811,
bit_entropy::_1_000};
std::vector<double> entropy(num_entropy_levels);
std::transform(entropy_levels.cbegin(), entropy_levels.cend(), entropy.begin(), [](bit_entropy entropy) {
const thrust::device_vector<TestType> data = generate(1 << 24, entropy);
return compute_actual_entropy(data);
});
REQUIRE(std::is_sorted(entropy.begin(), entropy.end(), less_t{}));
REQUIRE(std::unique(entropy.begin(), entropy.end()) == entropy.end());
}
TEST_CASE("Generators support bool", "[gen]")
{
constexpr int num_entropy_levels = 6;
std::array<bit_entropy, num_entropy_levels> entropy_levels{
bit_entropy::_0_000,
bit_entropy::_0_201,
bit_entropy::_0_337,
bit_entropy::_0_544,
bit_entropy::_0_811,
bit_entropy::_1_000};
std::vector<std::size_t> number_of_set(num_entropy_levels);
std::transform(entropy_levels.cbegin(), entropy_levels.cend(), number_of_set.begin(), [](bit_entropy entropy) {
const thrust::device_vector<bool> data = generate(1 << 24, entropy);
return thrust::count(data.begin(), data.end(), true);
});
REQUIRE(std::is_sorted(number_of_set.begin(), number_of_set.end()));
REQUIRE(std::unique(number_of_set.begin(), number_of_set.end()) == number_of_set.end());
}

View File

@@ -0,0 +1,49 @@
// SPDX-FileCopyrightText: Copyright (c) 2011-2023, NVIDIA CORPORATION. All rights reserved.
// SPDX-License-Identifier: BSD-3
#include <thrust/device_vector.h>
#include <thrust/host_vector.h>
#include <algorithm>
#include <cmath>
#include <nvbench_helper.cuh>
#include <boost/math/statistics/anderson_darling.hpp>
#include <boost/math/statistics/univariate_statistics.hpp>
#include <catch2/catch_template_test_macros.hpp>
#include <catch2/catch_test_macros.hpp>
bool is_normal(thrust::host_vector<double> data)
{
std::sort(data.begin(), data.end());
const double A2 = boost::math::statistics::anderson_darling_normality_statistic(data);
return A2 / data.size() < 0.05;
}
using types = nvbench::type_list<uint32_t, uint64_t>;
TEMPLATE_LIST_TEST_CASE("Generators produce power law distributed data", "[gen][power-law]", types)
{
const std::size_t elements = 1 << 28;
const std::size_t segments = 4 * 1024;
const thrust::device_vector<TestType> d_segment_offsets = generate.power_law.segment_offsets(elements, segments);
REQUIRE(d_segment_offsets.size() == segments + 1);
std::size_t actual_elements = 0;
thrust::host_vector<double> log_sizes(segments);
const thrust::host_vector<TestType> h_segment_offsets = d_segment_offsets;
for (std::size_t i = 0; i < segments; ++i)
{
const TestType begin = h_segment_offsets[i];
const TestType end = h_segment_offsets[i + 1];
REQUIRE(begin <= end);
const std::size_t size = end - begin;
actual_elements += size;
log_sizes[i] = std::log(size);
}
REQUIRE(actual_elements == elements);
REQUIRE(is_normal(std::move(log_sizes)));
}

View File

@@ -0,0 +1,37 @@
// SPDX-FileCopyrightText: Copyright (c) 2011-2023, NVIDIA CORPORATION. All rights reserved.
// SPDX-License-Identifier: BSD-3
#include <thrust/device_vector.h>
#include <thrust/extrema.h>
#include <limits>
#include <nvbench_helper.cuh>
#include <catch2/catch_template_test_macros.hpp>
#include <catch2/generators/catch_generators_all.hpp>
using types =
nvbench::type_list<int8_t,
int16_t,
int32_t,
int64_t,
#if _CCCL_HAS_INT128()
int128_t,
#endif
float,
double>;
TEMPLATE_LIST_TEST_CASE("Generators produce data within specified range", "[gen]", types)
{
const auto min = static_cast<TestType>(GENERATE_COPY(take(3, random(-124, 0))));
const auto max = static_cast<TestType>(GENERATE_COPY(take(3, random(0, 124))));
const thrust::device_vector<TestType> data = generate(1 << 16, bit_entropy::_1_000, min, max);
const TestType min_element = *thrust::min_element(data.begin(), data.end());
const TestType max_element = *thrust::max_element(data.begin(), data.end());
REQUIRE(min_element >= min);
REQUIRE(max_element <= max);
}

View File

@@ -0,0 +1,34 @@
// SPDX-FileCopyrightText: Copyright (c) 2011-2023, NVIDIA CORPORATION. All rights reserved.
// SPDX-License-Identifier: BSD-3
#include <thrust/device_vector.h>
#include <thrust/equal.h>
#include <nvbench_helper.cuh>
#include <catch2/catch_template_test_macros.hpp>
using types = nvbench::type_list<
bool,
int8_t,
int16_t,
int32_t,
int64_t,
#if _CCCL_HAS_INT128()
int128_t,
#endif
float,
double,
complex32,
complex64>;
TEMPLATE_LIST_TEST_CASE("Generator seeds the data", "[gen]", types)
{
auto generator = generate(1 << 24, bit_entropy::_0_811);
const thrust::device_vector<TestType> vec_1 = generator;
const thrust::device_vector<TestType> vec_2 = generator;
REQUIRE(vec_1.size() == vec_2.size());
REQUIRE_FALSE(thrust::equal(vec_1.begin(), vec_1.end(), vec_2.begin()));
}

View File

@@ -0,0 +1,204 @@
// SPDX-FileCopyrightText: Copyright (c) 2011-2023, NVIDIA CORPORATION. All rights reserved.
// SPDX-License-Identifier: BSD-3
#include <thrust/count.h>
#include <thrust/device_vector.h>
#include <thrust/host_vector.h>
#include <cmath>
#include <limits>
#include <map>
#include <nvbench_helper.cuh>
#include <boost/math/distributions/chi_squared.hpp>
#include <catch2/catch_template_test_macros.hpp>
#include <catch2/catch_test_macros.hpp>
#include <catch2/generators/catch_generators_all.hpp>
template <typename T>
bool is_uniform(thrust::host_vector<T> data, T min, T max)
{
const double value_range = static_cast<double>(max) - min;
const bool exact_binning = value_range < (1 << 20);
const int number_of_bins = exact_binning ? static_cast<int>(max - min + 1) : static_cast<int>(std::sqrt(data.size()));
thrust::host_vector<int> bins(number_of_bins, 0);
const double interval = value_range / static_cast<double>(number_of_bins);
const double expected_count = static_cast<double>(data.size()) / number_of_bins;
for (T val : data)
{
int bin_index = exact_binning ? val - min : (val - static_cast<double>(min)) / interval;
if (bin_index >= 0 && bin_index < number_of_bins)
{
bins[bin_index]++;
}
}
double chi_square = 0.0;
for (const auto& count : bins)
{
chi_square += std::pow(count - expected_count, 2) / expected_count;
}
boost::math::chi_squared_distribution<double> chi_squared_dist(number_of_bins - 1);
const double confidence = 0.95;
const double critical_value = boost::math::quantile(chi_squared_dist, confidence);
return chi_square <= critical_value;
}
using types =
nvbench::type_list<int8_t,
int16_t,
int32_t,
int64_t,
#if _CCCL_HAS_INT128()
int128_t,
#endif
float,
double>;
TEMPLATE_LIST_TEST_CASE("Generators produce uniformly distributed data", "[gen][uniform]", types)
{
const std::size_t elements = 1 << GENERATE_COPY(16, 20, 24, 28);
const TestType min = ::cuda::std::numeric_limits<TestType>::min();
const TestType max = ::cuda::std::numeric_limits<TestType>::max();
const thrust::device_vector<TestType> data = generate(elements, bit_entropy::_1_000, min, max);
REQUIRE(is_uniform<TestType>(data, min, max));
}
struct complex_to_real_t
{
template <typename T>
__host__ __device__ T operator()(const cuda::std::complex<T>& c) const
{
return c.real();
}
};
struct complex_to_imag_t
{
template <typename T>
__host__ __device__ T operator()(const cuda::std::complex<T>& c) const
{
return c.imag();
}
};
using complex_value_types = nvbench::type_list<float, double>;
TEMPLATE_LIST_TEST_CASE("Generators produce uniformly distributed complex", "[gen]", complex_value_types)
{
using value_type = TestType;
const auto min = ::cuda::std::numeric_limits<value_type>::min();
const auto max = ::cuda::std::numeric_limits<value_type>::max();
const thrust::device_vector<cuda::std::complex<value_type>> data = generate(1 << 16);
thrust::device_vector<value_type> component(data.size());
thrust::transform(data.begin(), data.end(), component.begin(), complex_to_real_t());
REQUIRE(is_uniform<value_type>(component, min, max));
thrust::transform(data.begin(), data.end(), component.begin(), complex_to_imag_t());
REQUIRE(is_uniform<value_type>(component, min, max));
}
TEST_CASE("Generators produce uniformly distributed bools", "[gen]")
{
const thrust::device_vector<bool> data = generate(1 << 24, bit_entropy::_0_544);
const std::size_t falses = thrust::count(data.begin(), data.end(), false);
const std::size_t trues = thrust::count(data.begin(), data.end(), true);
REQUIRE(falses > 0);
REQUIRE(trues > 0);
REQUIRE(falses + trues == data.size());
const double ratio = static_cast<double>(falses) / trues;
REQUIRE(ratio > 0.7);
}
using offsets = nvbench::type_list<uint32_t, uint64_t>;
TEMPLATE_LIST_TEST_CASE("Generators produce uniformly distributed offsets", "[gen]", offsets)
{
const std::size_t min_segment_size = 1;
const std::size_t max_segment_size = 256;
const std::size_t elements = 1 << GENERATE_COPY(16, 20, 24, 28);
const thrust::device_vector<TestType> d_segments =
generate.uniform.segment_offsets(elements, min_segment_size, max_segment_size);
const thrust::host_vector<TestType> h_segments = d_segments;
const std::size_t num_segments = h_segments.size() - 1;
std::size_t actual_elements = 0;
thrust::host_vector<int> segment_sizes(num_segments);
for (std::size_t sid = 0; sid < num_segments; sid++)
{
const TestType begin = h_segments[sid];
const TestType end = h_segments[sid + 1];
REQUIRE(begin <= end);
const TestType size = end - begin;
REQUIRE(size >= min_segment_size);
REQUIRE(size <= max_segment_size);
segment_sizes[sid] = size;
actual_elements += size;
}
REQUIRE(actual_elements == elements);
REQUIRE(is_uniform<int>(std::move(segment_sizes), min_segment_size, max_segment_size));
}
TEMPLATE_LIST_TEST_CASE("Generators produce uniformly distributed key segments", "[gen]", types)
try
{
const std::size_t min_segment_size = 1;
const std::size_t max_segment_size = 128;
const std::size_t elements = 1 << GENERATE_COPY(16, 20, 24, 28);
const thrust::device_vector<TestType> d_keys =
generate.uniform.key_segments(elements, min_segment_size, max_segment_size);
REQUIRE(d_keys.size() == elements);
const thrust::host_vector<TestType> h_keys = d_keys;
thrust::host_vector<std::size_t> segment_sizes;
TestType prev = h_keys[0];
std::size_t length = 1;
for (std::size_t kid = 1; kid < elements; kid++)
{
TestType next = h_keys[kid];
if (next == prev)
{
length++;
}
else
{
REQUIRE(length >= min_segment_size);
REQUIRE(length <= max_segment_size);
segment_sizes.push_back(length);
prev = next;
length = 1;
}
}
REQUIRE(length >= min_segment_size);
REQUIRE(length <= max_segment_size);
segment_sizes.push_back(length);
REQUIRE(is_uniform(std::move(segment_sizes), min_segment_size, max_segment_size));
}
catch (std::bad_alloc&)
{
// Skip test on OOM.
}