CCCL (CUDA C++ Core Libraries) provides: - CUB: device/block/warp-level GPU primitives (reduce, scan, sort, topk) - Thrust: high-level parallel algorithms (transform_reduce, sort, scan) - libcudacxx: CUDA C++ standard library (atomics, barriers, memory) - cudax: experimental features (memory resources, allocators) - Tuning policies: per-SM hardware-specific algorithm parameters Competition optimization vectors mapped to CCCL: - Output TPS (83% weight): warp_reduce, block_reduce, device_topk - Input TPS (14% weight): device_scan, block_load, prefetch - Cache TPS (3% weight): prefix caching strategy patterns - Memory (0.9 util): pooled/cached/buddy allocators Source: https://github.com/NVIDIA/cccl (shallow clone, HEAD only) License: Apache-2.0
642 lines
21 KiB
Plaintext
642 lines
21 KiB
Plaintext
// SPDX-FileCopyrightText: Copyright (c) 2023, NVIDIA CORPORATION. All rights reserved.
|
|
// SPDX-License-Identifier: BSD-3
|
|
|
|
#include <cub/block/radix_rank_sort_operations.cuh>
|
|
|
|
#include <thrust/detail/raw_pointer_cast.h>
|
|
|
|
#include <bitset>
|
|
#include <climits>
|
|
#include <limits>
|
|
#include <type_traits>
|
|
|
|
#include <c2h/catch2_test_helper.h>
|
|
|
|
template <typename KeyT>
|
|
struct fundamental_extractor_t
|
|
{
|
|
std::uint32_t bit_start;
|
|
std::uint32_t mask; // NOLINT(modernize-use-default-member-init)
|
|
|
|
__host__ __device__ fundamental_extractor_t(std::uint32_t bit_start = 0, std::uint32_t num_bits = 0)
|
|
: bit_start(bit_start)
|
|
, mask((1 << num_bits) - 1)
|
|
{}
|
|
|
|
__host__ __device__ std::uint32_t Digit(KeyT key) const
|
|
{
|
|
return std::uint32_t(key >> KeyT(bit_start)) & mask;
|
|
}
|
|
};
|
|
|
|
template <class T>
|
|
c2h::host_vector<std::uint8_t> get_random_buffer()
|
|
{
|
|
c2h::device_vector<std::uint8_t> buffer(sizeof(T));
|
|
c2h::gen(C2H_SEED(3), buffer);
|
|
return buffer;
|
|
}
|
|
|
|
constexpr int max_digit_bits = sizeof(std::uint32_t) * CHAR_BIT;
|
|
using digit_bits_t = std::bitset<max_digit_bits>;
|
|
|
|
digit_bits_t buffer_to_digit_bits(const char* buffer, int current_bit, int num_bits)
|
|
{
|
|
digit_bits_t dst; // all bits set to zero
|
|
|
|
for (int bit = current_bit; bit < current_bit + num_bits; bit++)
|
|
{
|
|
const int dst_bit = bit - current_bit;
|
|
const int src_byte = bit / CHAR_BIT;
|
|
const int src_bit = bit % CHAR_BIT;
|
|
|
|
std::bitset<CHAR_BIT> src(buffer[src_byte]);
|
|
dst[dst_bit] = src[src_bit];
|
|
}
|
|
|
|
return dst;
|
|
}
|
|
|
|
using fundamental_types = c2h::type_list<std::uint8_t, std::uint16_t, std::uint32_t, std::uint64_t>;
|
|
using a_few_fundamental_types = c2h::type_list<std::uint8_t, std::uint64_t>;
|
|
|
|
/**
|
|
* This test checks that radix operations can extract certain bits out of unsigned integers.
|
|
* Test runs for all possible combinations of `current_bit` and `num_bits`.
|
|
* Example for `current_bit = 5`, and `num_bits = 4`:
|
|
*
|
|
* [-------]
|
|
* src: 1 1 0 0 1 1 0 0 1 1
|
|
* bit: 9 8 7 6 5 4 3 2 1 0
|
|
* dst: 0 0 0 0 0 0 1 0 0 1
|
|
*
|
|
*/
|
|
C2H_TEST("Radix operations extract digits from fundamental types", "[radix][operations]", fundamental_types)
|
|
{
|
|
using key_t = typename c2h::get<0, TestType>;
|
|
using traits = cub::detail::radix::traits_t<key_t>;
|
|
using extractor_t = fundamental_extractor_t<key_t>;
|
|
using decomposer_t = cub::detail::identity_decomposer_t;
|
|
|
|
auto decomposer = decomposer_t{};
|
|
constexpr int max_key_bits = sizeof(key_t) * CHAR_BIT;
|
|
REQUIRE(traits::default_end_bit(decomposer) == max_key_bits);
|
|
|
|
key_t val{};
|
|
c2h::host_vector<char> output_buffer_mem(sizeof(std::uint32_t));
|
|
const c2h::host_vector<char> input_buffer_mem = get_random_buffer<key_t>();
|
|
|
|
char* output_buffer = thrust::raw_pointer_cast(output_buffer_mem.data());
|
|
const char* input_buffer = thrust::raw_pointer_cast(input_buffer_mem.data());
|
|
std::memcpy(&val, input_buffer, sizeof(key_t));
|
|
|
|
for (int current_bit = 0; current_bit < max_key_bits; current_bit++)
|
|
{
|
|
const int max_bits = std::min(max_key_bits - current_bit, max_digit_bits);
|
|
|
|
for (int num_bits = 1; num_bits < max_bits; num_bits++)
|
|
{
|
|
auto extractor = traits::template digit_extractor<extractor_t>(current_bit, num_bits, decomposer);
|
|
|
|
std::uint32_t digit = extractor.Digit(val);
|
|
std::memcpy(output_buffer, &digit, sizeof(std::uint32_t));
|
|
|
|
digit_bits_t result = buffer_to_digit_bits(output_buffer, 0, num_bits);
|
|
digit_bits_t reference = buffer_to_digit_bits(input_buffer, current_bit, num_bits);
|
|
|
|
REQUIRE(reference == result);
|
|
}
|
|
}
|
|
}
|
|
|
|
template <class T>
|
|
struct tuple_decomposer_t;
|
|
|
|
template <class... Ts>
|
|
struct tuple_decomposer_t<cuda::std::tuple<Ts...>>
|
|
{
|
|
template <std::size_t... Is>
|
|
__host__ __device__ cuda::std::tuple<Ts&...>
|
|
extract(cuda::std::tuple<Ts...>& key, cuda::std::index_sequence<Is...>) const
|
|
{
|
|
return cuda::std::tie(cuda::std::get<Is>(key)...);
|
|
}
|
|
|
|
__host__ __device__ cuda::std::tuple<Ts&...> operator()(cuda::std::tuple<Ts...>& key) const
|
|
{
|
|
return extract(key, cuda::std::make_index_sequence<sizeof...(Ts)>{});
|
|
}
|
|
};
|
|
|
|
// clang-format off
|
|
template <std::size_t I, class... Ts>
|
|
cuda::std::enable_if_t<I == 0>
|
|
buffer_to_tpl_helper(const char *buffer, cuda::std::tuple<Ts...> &tpl)
|
|
{
|
|
constexpr std::size_t element_size =
|
|
sizeof(cuda::std::tuple_element_t<I, cuda::std::tuple<Ts...>>);
|
|
std::memcpy(&cuda::std::get<I>(tpl), buffer, element_size);
|
|
}
|
|
|
|
template <std::size_t I, class... Ts>
|
|
cuda::std::enable_if_t <I != 0>
|
|
buffer_to_tpl_helper(const char *buffer, cuda::std::tuple<Ts...> &tpl)
|
|
{
|
|
constexpr std::size_t element_size =
|
|
sizeof(cuda::std::tuple_element_t<I, cuda::std::tuple<Ts...>>);
|
|
std::memcpy(&cuda::std::get<I>(tpl), buffer, element_size);
|
|
buffer_to_tpl_helper<I - 1>(buffer + element_size, tpl);
|
|
}
|
|
|
|
template <class... Ts>
|
|
void buffer_to_tpl(const char *buffer, cuda::std::tuple<Ts...> &tpl)
|
|
{
|
|
buffer_to_tpl_helper<sizeof...(Ts) - 1>(buffer, tpl);
|
|
}
|
|
|
|
template <std::size_t I, class... Ts>
|
|
cuda::std::enable_if_t<I == 0>
|
|
tpl_to_buffer_helper(char *buffer, cuda::std::tuple<Ts...> &tpl)
|
|
{
|
|
constexpr std::size_t element_size =
|
|
sizeof(cuda::std::tuple_element_t<I, cuda::std::tuple<Ts...>>);
|
|
std::memcpy(buffer, &cuda::std::get<I>(tpl), element_size);
|
|
}
|
|
|
|
template <std::size_t I, class... Ts>
|
|
cuda::std::enable_if_t <I != 0>
|
|
tpl_to_buffer_helper(char *buffer, cuda::std::tuple<Ts...> &tpl)
|
|
{
|
|
constexpr std::size_t element_size =
|
|
sizeof(cuda::std::tuple_element_t<I, cuda::std::tuple<Ts...>>);
|
|
std::memcpy(buffer, &cuda::std::get<I>(tpl), element_size);
|
|
tpl_to_buffer_helper<I - 1>(buffer + element_size, tpl);
|
|
}
|
|
|
|
template <class... Ts>
|
|
void tpl_to_buffer(char *buffer, cuda::std::tuple<Ts...> &tpl)
|
|
{
|
|
tpl_to_buffer_helper<sizeof...(Ts) - 1>(buffer, tpl);
|
|
}
|
|
|
|
template <std::size_t I = 0, class... Ts>
|
|
cuda::std::enable_if_t<I >= sizeof...(Ts), int>
|
|
tpl_to_max_bits(cuda::std::tuple<Ts...> &)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
template <std::size_t I = 0, class... Ts>
|
|
cuda::std::enable_if_t <I < sizeof...(Ts), int>
|
|
tpl_to_max_bits(cuda::std::tuple<Ts...> &tpl)
|
|
{
|
|
constexpr std::size_t element_size =
|
|
sizeof(cuda::std::tuple_element_t<I, cuda::std::tuple<Ts...>>);
|
|
return element_size * CHAR_BIT + tpl_to_max_bits<I + 1>(tpl);
|
|
}
|
|
|
|
template <std::size_t I = 0, class... Ts>
|
|
cuda::std::enable_if_t<I >= sizeof...(Ts)>
|
|
tpl_to_min(cuda::std::tuple<Ts...> &)
|
|
{}
|
|
|
|
template <std::size_t I = 0, class... Ts>
|
|
cuda::std::enable_if_t <I < sizeof...(Ts)>
|
|
tpl_to_min(cuda::std::tuple<Ts...> &tpl)
|
|
{
|
|
using T = cuda::std::tuple_element_t<I, cuda::std::tuple<Ts...>>;
|
|
cuda::std::get<I>(tpl) = cuda::std::numeric_limits<T>::lowest();
|
|
tpl_to_min<I + 1>(tpl);
|
|
}
|
|
|
|
template <std::size_t I = 0, class... Ts>
|
|
cuda::std::enable_if_t<I >= sizeof...(Ts)>
|
|
tpl_to_max(cuda::std::tuple<Ts...> &)
|
|
{}
|
|
|
|
template <std::size_t I = 0, class... Ts>
|
|
cuda::std::enable_if_t <I < sizeof...(Ts)>
|
|
tpl_to_max(cuda::std::tuple<Ts...> &tpl)
|
|
{
|
|
using T = cuda::std::tuple_element_t<I, cuda::std::tuple<Ts...>>;
|
|
cuda::std::get<I>(tpl) = cuda::std::numeric_limits<T>::max();
|
|
tpl_to_max<I + 1>(tpl);
|
|
}
|
|
// clang-format on
|
|
|
|
/**
|
|
* This test checks that radix operations can extract certain bits out of aggregate types.
|
|
* Test runs for all possible combinations of `current_bit` and `num_bits` excluding padding bits.
|
|
* For example, `struct custom_t { short s = 65535; float f = -42.2f; };` has the following binary
|
|
* representation:
|
|
*
|
|
* <------------ `.f` ------------><-- padding ---><---- `.s` ---->
|
|
* s< exp. ><----- mantissa ------><-- padding ---><--- short ---->
|
|
* 1100000010000110011001100110011000000000000000001111111111111111
|
|
* +---~ ~--+
|
|
* < <---- higher bits / lower bits ----> >
|
|
*
|
|
* For `current_bit = 12`, and `num_bits = 9`:
|
|
* dst: 0000011011111
|
|
* < fp ><sh>
|
|
*
|
|
*/
|
|
template <class... Ts>
|
|
void test_tuple()
|
|
{
|
|
using tpl_t = cuda::std::tuple<Ts...>;
|
|
using traits = cub::detail::radix::traits_t<tpl_t>;
|
|
using decomposer_t = tuple_decomposer_t<tpl_t>;
|
|
using extractor_t = cub::detail::radix::custom_digit_extractor_t<decomposer_t>;
|
|
|
|
tpl_t tpl{};
|
|
c2h::host_vector<char> output_buffer_mem(sizeof(std::uint32_t));
|
|
const c2h::host_vector<char> input_buffer_mem = get_random_buffer<tpl_t>();
|
|
|
|
char* output_buffer = thrust::raw_pointer_cast(output_buffer_mem.data());
|
|
const char* input_buffer = thrust::raw_pointer_cast(input_buffer_mem.data());
|
|
buffer_to_tpl(input_buffer, tpl);
|
|
|
|
auto decomposer = decomposer_t{};
|
|
const int max_key_bits = tpl_to_max_bits(tpl);
|
|
REQUIRE(traits::default_end_bit(decomposer) == max_key_bits);
|
|
|
|
for (int current_bit = 0; current_bit < max_key_bits; current_bit++)
|
|
{
|
|
const int max_bits = std::min(max_key_bits - current_bit, max_digit_bits);
|
|
|
|
for (int num_bits = 1; num_bits < max_bits; num_bits++)
|
|
{
|
|
auto extractor = traits::template digit_extractor<extractor_t>(current_bit, num_bits, decomposer);
|
|
|
|
std::uint32_t digit = extractor.Digit(tpl);
|
|
std::memcpy(output_buffer, &digit, sizeof(std::uint32_t));
|
|
|
|
digit_bits_t result = buffer_to_digit_bits(output_buffer, 0, num_bits);
|
|
digit_bits_t reference = buffer_to_digit_bits(input_buffer, current_bit, num_bits);
|
|
|
|
// Provides readable error messages:
|
|
// 00000000000000000000000000000000
|
|
// ==
|
|
// 00000000000000000000000000000001
|
|
REQUIRE(reference == result);
|
|
}
|
|
}
|
|
}
|
|
|
|
C2H_TEST("Radix operations extract digits from pairs", "[radix][operations]", fundamental_types, fundamental_types)
|
|
{
|
|
test_tuple<typename c2h::get<0, TestType>, //
|
|
typename c2h::get<1, TestType>>();
|
|
}
|
|
|
|
C2H_TEST("Radix operations extract digits from triples",
|
|
"[radix][operations]",
|
|
fundamental_types,
|
|
fundamental_types,
|
|
fundamental_types)
|
|
{
|
|
test_tuple<typename c2h::get<0, TestType>, //
|
|
typename c2h::get<1, TestType>, //
|
|
typename c2h::get<2, TestType>>();
|
|
}
|
|
|
|
C2H_TEST("Radix operations extract digits from tetrads",
|
|
"[radix][operations]",
|
|
a_few_fundamental_types,
|
|
a_few_fundamental_types,
|
|
a_few_fundamental_types,
|
|
a_few_fundamental_types)
|
|
{
|
|
test_tuple<typename c2h::get<0, TestType>, //
|
|
typename c2h::get<1, TestType>, //
|
|
typename c2h::get<2, TestType>, //
|
|
typename c2h::get<3, TestType>>();
|
|
}
|
|
|
|
/**
|
|
* This test checks that radix operations can invert bits (`~`) of fundamental types.
|
|
*
|
|
* src: 1 1 0 0 1 1 0 0 1 1
|
|
* dst: 0 0 1 1 0 0 1 1 0 0
|
|
*
|
|
*/
|
|
C2H_TEST("Radix operations inverse fundamental types", "[radix][operations]", fundamental_types)
|
|
{
|
|
using key_t = typename c2h::get<0, TestType>;
|
|
using traits = cub::detail::radix::traits_t<key_t>;
|
|
using extractor_t = fundamental_extractor_t<key_t>;
|
|
using decomposer_t = cub::detail::identity_decomposer_t;
|
|
|
|
auto decomposer = decomposer_t{};
|
|
|
|
key_t val{};
|
|
c2h::host_vector<char> output_buffer_mem(sizeof(key_t));
|
|
c2h::host_vector<char> input_buffer_mem = get_random_buffer<key_t>();
|
|
|
|
char* output_buffer = thrust::raw_pointer_cast(output_buffer_mem.data());
|
|
char* input_buffer = thrust::raw_pointer_cast(input_buffer_mem.data());
|
|
std::memcpy(&val, input_buffer, sizeof(key_t));
|
|
|
|
for (std::size_t i = 0; i < input_buffer_mem.size(); i++)
|
|
{
|
|
input_buffer[i] = static_cast<char>(~input_buffer[i]);
|
|
}
|
|
|
|
key_t inv = traits::bit_ordered_inversion_policy::inverse(decomposer, val);
|
|
std::memcpy(output_buffer, &inv, sizeof(key_t));
|
|
|
|
REQUIRE(input_buffer_mem == output_buffer_mem);
|
|
}
|
|
|
|
/**
|
|
* This test checks that radix operations can invert bits (`~`) of aggregate types.
|
|
* For example, `struct custom_t { short s = 65535; float f = -42.2f; };`:
|
|
*
|
|
* <------------ `.f` ------------><-- padding ---><---- `.s` ---->
|
|
* s< exp. ><----- mantissa ------><-- padding ---><--- short ---->
|
|
* src: 1100000010000110011001100110011000000000000000001111111111111111
|
|
* +------------------------------~ ~--------------+
|
|
* dst: 0011111101111001100110011001100111111111111111110000000000000000
|
|
* < <---- higher bits / lower bits ----> >
|
|
*
|
|
*/
|
|
C2H_TEST("Radix operations inverse pairs", "[radix][operations]", fundamental_types, fundamental_types)
|
|
{
|
|
using tpl_t = cuda::std::tuple<typename c2h::get<0, TestType>, //
|
|
typename c2h::get<1, TestType>>;
|
|
|
|
using traits = cub::detail::radix::traits_t<tpl_t>;
|
|
using decomposer_t = tuple_decomposer_t<tpl_t>;
|
|
using extractor_t = cub::detail::radix::custom_digit_extractor_t<decomposer_t>;
|
|
|
|
auto decomposer = decomposer_t{};
|
|
|
|
tpl_t tpl{};
|
|
c2h::host_vector<char> input_buffer_mem = get_random_buffer<tpl_t>();
|
|
|
|
char* input_buffer = thrust::raw_pointer_cast(input_buffer_mem.data());
|
|
buffer_to_tpl(input_buffer, tpl);
|
|
|
|
for (std::size_t i = 0; i < input_buffer_mem.size(); i++)
|
|
{
|
|
input_buffer[i] = static_cast<char>(~input_buffer[i]);
|
|
}
|
|
|
|
c2h::host_vector<char> output_buffer_mem = input_buffer_mem;
|
|
char* output_buffer = thrust::raw_pointer_cast(output_buffer_mem.data());
|
|
|
|
tpl_t inv = traits::bit_ordered_inversion_policy::inverse(decomposer, tpl);
|
|
tpl_to_buffer(output_buffer, inv);
|
|
|
|
REQUIRE(input_buffer_mem == output_buffer_mem);
|
|
}
|
|
|
|
/**
|
|
* This tests checks that radix operations can get a value that when converted
|
|
* to binary-comparable representation, yields smallest possible value.
|
|
*/
|
|
C2H_TEST("Radix operations infere minimal value for fundamental types", "[radix][operations]", fundamental_types)
|
|
{
|
|
using key_t = typename c2h::get<0, TestType>;
|
|
using traits = cub::detail::radix::traits_t<key_t>;
|
|
using decomposer_t = cub::detail::identity_decomposer_t;
|
|
|
|
c2h::host_vector<char> output_buffer_mem(sizeof(key_t));
|
|
c2h::host_vector<char> input_buffer_mem(sizeof(key_t));
|
|
|
|
key_t ref = cuda::std::numeric_limits<key_t>::lowest();
|
|
key_t val = traits::min_raw_binary_key(decomposer_t{});
|
|
|
|
REQUIRE(ref == val);
|
|
}
|
|
|
|
C2H_TEST(
|
|
"Radix operations infere minimal value for pair types", "[radix][operations]", fundamental_types, fundamental_types)
|
|
{
|
|
using tpl_t = cuda::std::tuple<typename c2h::get<0, TestType>, //
|
|
typename c2h::get<1, TestType>>;
|
|
|
|
using traits = cub::detail::radix::traits_t<tpl_t>;
|
|
using decomposer_t = tuple_decomposer_t<tpl_t>;
|
|
|
|
tpl_t ref;
|
|
tpl_to_min(ref);
|
|
|
|
tpl_t val = traits::min_raw_binary_key(decomposer_t{});
|
|
|
|
REQUIRE(ref == val);
|
|
}
|
|
|
|
/**
|
|
* This tests checks that radix operations can get a value that when converted
|
|
* to binary-comparable representation, yields largest possible value.
|
|
*/
|
|
C2H_TEST("Radix operations infere maximal value for fundamental types", "[radix][operations]", fundamental_types)
|
|
{
|
|
using key_t = typename c2h::get<0, TestType>;
|
|
using traits = cub::detail::radix::traits_t<key_t>;
|
|
using decomposer_t = cub::detail::identity_decomposer_t;
|
|
|
|
key_t ref = cuda::std::numeric_limits<key_t>::max();
|
|
key_t val = traits::max_raw_binary_key(decomposer_t{});
|
|
|
|
REQUIRE(ref == val);
|
|
}
|
|
|
|
C2H_TEST(
|
|
"Radix operations infere maximal value for pair types", "[radix][operations]", fundamental_types, fundamental_types)
|
|
{
|
|
using tpl_t = cuda::std::tuple<typename c2h::get<0, TestType>, //
|
|
typename c2h::get<1, TestType>>;
|
|
|
|
using traits = cub::detail::radix::traits_t<tpl_t>;
|
|
using decomposer_t = tuple_decomposer_t<tpl_t>;
|
|
|
|
tpl_t ref;
|
|
tpl_to_max(ref);
|
|
|
|
tpl_t val = traits::max_raw_binary_key(decomposer_t{});
|
|
|
|
REQUIRE(ref == val);
|
|
}
|
|
|
|
using fundamental_signed_types = c2h::type_list<std::int8_t, std::int16_t, std::int32_t, std::int64_t>;
|
|
|
|
/**
|
|
* This tests checks that radix operations can convert a value to a binary-comparable
|
|
* representation. For example, `42.0f` is larger than `-42.0f`, but if we look at the
|
|
* binary representation, it's not the case because of the sign bit:
|
|
*
|
|
* s< exp. ><----- mantissa ------>
|
|
* 42.0f: 01000010001010000000000000000000
|
|
* -42.0f: 11000010001010000000000000000000
|
|
*
|
|
*/
|
|
C2H_TEST("Radix operations reorder values for pair types",
|
|
"[radix][operations]",
|
|
fundamental_signed_types,
|
|
fundamental_signed_types)
|
|
{
|
|
using T1 = typename c2h::get<0, TestType>;
|
|
using UT1 = std::make_unsigned_t<T1>;
|
|
using T2 = typename c2h::get<1, TestType>;
|
|
using UT2 = std::make_unsigned_t<T2>;
|
|
using tpl_t = cuda::std::tuple<T1, T2>;
|
|
|
|
using traits = cub::detail::radix::traits_t<tpl_t>;
|
|
using conversion_policy = typename traits::bit_ordered_conversion_policy;
|
|
using decomposer_t = tuple_decomposer_t<tpl_t>;
|
|
|
|
std::bitset<sizeof(T1) * CHAR_BIT> bs_1;
|
|
std::bitset<sizeof(T2) * CHAR_BIT> bs_2;
|
|
|
|
// 10000(0)
|
|
bs_1.set(sizeof(T1) * CHAR_BIT - 1);
|
|
bs_2.set(sizeof(T2) * CHAR_BIT - 1);
|
|
|
|
UT1 ul_1 = static_cast<UT1>(bs_1.to_ullong());
|
|
UT2 ul_2 = static_cast<UT2>(bs_2.to_ullong());
|
|
|
|
T1 l_1 = reinterpret_cast<T1&>(ul_1);
|
|
T2 l_2 = reinterpret_cast<T2&>(ul_2);
|
|
|
|
REQUIRE(l_1 == cuda::std::numeric_limits<T1>::lowest());
|
|
REQUIRE(l_2 == cuda::std::numeric_limits<T2>::lowest());
|
|
|
|
{
|
|
tpl_t ref{T1{0}, T2{0}};
|
|
const tpl_t unordered_val = tpl_t{l_1, l_2};
|
|
const tpl_t ordered_val = conversion_policy::to_bit_ordered(decomposer_t{}, unordered_val);
|
|
|
|
REQUIRE(ref == ordered_val);
|
|
|
|
const tpl_t restored_val = conversion_policy::from_bit_ordered(decomposer_t{}, ordered_val);
|
|
REQUIRE(restored_val == unordered_val);
|
|
}
|
|
|
|
ul_1 = static_cast<UT1>(cuda::std::numeric_limits<T1>::max());
|
|
ul_2 = static_cast<UT2>(cuda::std::numeric_limits<T2>::max());
|
|
|
|
l_1 = reinterpret_cast<T1&>(ul_1);
|
|
l_2 = reinterpret_cast<T2&>(ul_2);
|
|
|
|
bs_1 = ul_1;
|
|
bs_2 = ul_2;
|
|
|
|
REQUIRE_FALSE(bs_1[sizeof(T1) * CHAR_BIT - 1]);
|
|
REQUIRE_FALSE(bs_2[sizeof(T2) * CHAR_BIT - 1]);
|
|
|
|
{
|
|
const tpl_t unordered_val = tpl_t{l_1, l_2};
|
|
const tpl_t ordered_val = conversion_policy::to_bit_ordered(decomposer_t{}, unordered_val);
|
|
|
|
ul_1 = reinterpret_cast<const UT1&>(cuda::std::get<0>(ordered_val));
|
|
ul_2 = reinterpret_cast<const UT2&>(cuda::std::get<1>(ordered_val));
|
|
|
|
REQUIRE(ul_1 == cuda::std::numeric_limits<UT1>::max());
|
|
REQUIRE(ul_2 == cuda::std::numeric_limits<UT2>::max());
|
|
|
|
const tpl_t restored_val = conversion_policy::from_bit_ordered(decomposer_t{}, ordered_val);
|
|
REQUIRE(restored_val == unordered_val);
|
|
}
|
|
}
|
|
|
|
struct fp_aggregate_t
|
|
{
|
|
double fp64;
|
|
float fp32;
|
|
};
|
|
|
|
struct fp_aggregate_decomposer_t
|
|
{
|
|
__host__ __device__ cuda::std::tuple<double&, float&> operator()(fp_aggregate_t& val) const
|
|
{
|
|
return {val.fp64, val.fp32};
|
|
}
|
|
};
|
|
|
|
struct flipped_fp_aggregate_decomposer_t
|
|
{
|
|
__host__ __device__ cuda::std::tuple<float&, double&> operator()(fp_aggregate_t& val) const
|
|
{
|
|
return {val.fp32, val.fp64};
|
|
}
|
|
};
|
|
|
|
/**
|
|
* This tests checks radix sort guarantees to treat +0/-0 as the same value.
|
|
*/
|
|
TEST_CASE("Radix operations treat -0/+0 as being equal", "[radix][operations]")
|
|
{
|
|
using traits = cub::detail::radix::traits_t<fp_aggregate_t>;
|
|
using conversion_policy = typename traits::bit_ordered_conversion_policy;
|
|
using decomposer_t = fp_aggregate_decomposer_t;
|
|
using extractor_t = cub::detail::radix::custom_digit_extractor_t<decomposer_t>;
|
|
|
|
fp_aggregate_t negative{-0.0, -0.0f};
|
|
fp_aggregate_t positive{+0.0, +0.0f};
|
|
fp_aggregate_t ordered_negative = conversion_policy::to_bit_ordered(decomposer_t{}, negative);
|
|
fp_aggregate_t ordered_positibe = conversion_policy::to_bit_ordered(decomposer_t{}, positive);
|
|
|
|
constexpr int num_bits = CHAR_BIT;
|
|
|
|
for (int bit = 0; bit < 8; bit += num_bits)
|
|
{
|
|
auto extractor = traits::digit_extractor<extractor_t>(bit, num_bits, decomposer_t{});
|
|
|
|
const std::uint32_t digit_positive = extractor.Digit(ordered_positibe);
|
|
const std::uint32_t digit_negative = extractor.Digit(ordered_negative);
|
|
|
|
REQUIRE(digit_positive == digit_negative);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* This tests checks that radix operations respect the order of fields in the
|
|
* tuple instead of looking at the binary key representation.
|
|
*/
|
|
TEST_CASE("Radix operations allow fields permutation", "[radix][operations]")
|
|
{
|
|
using traits = cub::detail::radix::traits_t<fp_aggregate_t>;
|
|
using conversion_policy = typename traits::bit_ordered_conversion_policy;
|
|
using decomposer_t = flipped_fp_aggregate_decomposer_t;
|
|
using extractor_t = cub::detail::radix::custom_digit_extractor_t<decomposer_t>;
|
|
|
|
fp_aggregate_t lhs{4.2, 2.4f};
|
|
fp_aggregate_t rhs{2.4, 4.2f};
|
|
|
|
REQUIRE(cuda::std::tie(lhs.fp64, lhs.fp32) > cuda::std::tie(rhs.fp64, rhs.fp32));
|
|
|
|
fp_aggregate_t ordered_lhs = conversion_policy::to_bit_ordered(decomposer_t{}, lhs);
|
|
fp_aggregate_t ordered_rhs = conversion_policy::to_bit_ordered(decomposer_t{}, lhs);
|
|
|
|
constexpr int num_bits = CHAR_BIT;
|
|
constexpr int aggregate_bits = (sizeof(float) + sizeof(double)) * CHAR_BIT;
|
|
|
|
for (int current_bit = aggregate_bits - num_bits; current_bit >= 0; current_bit -= num_bits)
|
|
{
|
|
auto extractor = traits::digit_extractor<extractor_t>(current_bit, num_bits, decomposer_t{});
|
|
|
|
const std::uint32_t digit_lhs = extractor.Digit(ordered_lhs);
|
|
const std::uint32_t digit_rhs = extractor.Digit(ordered_rhs);
|
|
|
|
if (digit_lhs == digit_rhs)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
std::bitset<32> bs_lhs(digit_lhs);
|
|
std::bitset<32> bs_rhs(digit_rhs);
|
|
|
|
for (int bit = 31; bit >= 0; bit--)
|
|
{
|
|
REQUIRE_FALSE(bs_lhs[bit]);
|
|
if (bs_rhs[bit])
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
}
|