[CCCL] 瘦身 + 补全: 移除 cudax/python/libcudacxx-tests 冗余文件, 新增 c2h 测试助手 + cmake 构建系统 + 8 个 CUDA thrust examples
变更摘要:
- 删除: cudax/ (783 files, 7.2M) — 实验性组件,竞赛不需要
- 删除: python/ (226 files, 2.0M) — Python 绑定,竞赛不需要
- 删除: libcudacxx/{test,benchmarks,codegen,cmake,share} (4432 files, 31M)
保留: libcudacxx/include/ (1463 headers, cuda::std 编译依赖)
- 新增: c2h/ (27 files) — CUB Catch2 测试辅助头文件,编译 243 个测试必需
- 新增: cmake/ (29 files) — CCCL 原生 CMake 构建系统
- 新增: thrust/examples/cuda/ (7 files) + cpp_integration/ (1 file)
async_reduce, custom_temporary_allocation, explicit_cuda_stream,
global_device_vector, range_view, unwrap_pointer, wrap_pointer, device
结果: cccl_upstream 从 74M→35M (瘦身 53%), 核心内容 100% 保留:
27/27 tuning headers, 78 benchmarks, 243 tests,
60 thrust examples, 18 CUB examples, 全部编译头文件
This commit is contained in:
@@ -1,92 +0,0 @@
|
||||
//===----------------------------------------------------------------------===//
|
||||
//
|
||||
// Part of CUDA Experimental in CUDA C++ Core Libraries,
|
||||
// under the Apache License v2.0 with LLVM Exceptions.
|
||||
// See https://llvm.org/LICENSE.txt for license information.
|
||||
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
||||
// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES.
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
// Temporary nvcc workaround __host__ __device__ dtor conflict in cuda::buffer
|
||||
#if defined(__CUDACC__)
|
||||
# pragma nv_diag_suppress 20011
|
||||
#endif
|
||||
|
||||
#include <cuda/memory_pool>
|
||||
#include <cuda/std/cstddef>
|
||||
#include <cuda/stream>
|
||||
|
||||
#include <cuda/experimental/__cuco/capacity.cuh>
|
||||
#include <cuda/experimental/__cuco/fixed_capacity_map.cuh>
|
||||
|
||||
#include <testing.cuh>
|
||||
|
||||
namespace cudax = cuda::experimental;
|
||||
|
||||
constexpr int empty_key = -1;
|
||||
constexpr int empty_value = -1;
|
||||
|
||||
C2H_TEST("fixed_capacity_map dynamic capacity — capacity() reflects the valid capacity", "[capacity][dynamic]")
|
||||
{
|
||||
constexpr ::cuda::std::size_t requested = 1000;
|
||||
using dyn_map_t = cudax::cuco::fixed_capacity_map<int, int>;
|
||||
|
||||
static_assert(dyn_map_t::capacity_v == ::cuda::std::dynamic_extent,
|
||||
"capacity_v must be dynamic_extent for dynamic-capacity maps");
|
||||
static_assert(dyn_map_t::ref_type::capacity_v == ::cuda::std::dynamic_extent,
|
||||
"ref capacity_v must be dynamic_extent for dynamic maps");
|
||||
|
||||
const auto valid =
|
||||
cudax::cuco::make_valid_capacity<dyn_map_t::probing_scheme_type, dyn_map_t::bucket_size>(requested);
|
||||
|
||||
::cuda::stream stream{::cuda::device_ref{0}};
|
||||
auto mr = ::cuda::device_default_memory_pool(::cuda::device_ref{0});
|
||||
|
||||
dyn_map_t map{stream, mr, requested, cudax::cuco::empty_key{empty_key}, cudax::cuco::empty_value{empty_value}};
|
||||
REQUIRE(map.capacity() == valid);
|
||||
REQUIRE(map.capacity() >= requested);
|
||||
}
|
||||
|
||||
C2H_TEST("fixed_capacity_map static capacity — valid capacity and capacity_v", "[capacity][static]")
|
||||
{
|
||||
// Double hashing rounds a requested slot count up to a prime-cycle capacity, so the valid capacity
|
||||
// must be computed from the probing scheme and bucket size before it can name a static map type.
|
||||
using probing = cudax::cuco::double_hashing<1, cudax::cuco::hash<int>>;
|
||||
[[maybe_unused]] constexpr int bucket = 1;
|
||||
|
||||
constexpr ::cuda::std::size_t requested = 1000;
|
||||
constexpr auto valid = cudax::cuco::make_valid_capacity<probing, bucket>(requested);
|
||||
static_assert(valid > requested, "1000 is not a valid double-hashing capacity; it rounds up");
|
||||
|
||||
using smap_t =
|
||||
cudax::cuco::fixed_capacity_map<int, int, valid, ::cuda::thread_scope_device, ::cuda::std::equal_to<int>, probing, 1>;
|
||||
static_assert(smap_t::capacity_v == valid, "the map type carries the valid capacity, not the request");
|
||||
static_assert(smap_t::ref_type::capacity_v == valid, "the ref carries the same valid capacity");
|
||||
|
||||
::cuda::stream stream{::cuda::device_ref{0}};
|
||||
auto mr = ::cuda::device_default_memory_pool(::cuda::device_ref{0});
|
||||
|
||||
smap_t map{stream, mr, cudax::cuco::empty_key{empty_key}, cudax::cuco::empty_value{empty_value}};
|
||||
REQUIRE(map.capacity() == valid);
|
||||
}
|
||||
|
||||
C2H_TEST("fixed_capacity_map dynamic extent — load factor constructor", "[capacity][dynamic][load_factor]")
|
||||
{
|
||||
constexpr int num_elements = 500;
|
||||
constexpr double load_factor = 0.5;
|
||||
|
||||
::cuda::stream stream{::cuda::device_ref{0}};
|
||||
auto mr = ::cuda::device_default_memory_pool(::cuda::device_ref{0});
|
||||
|
||||
cudax::cuco::fixed_capacity_map<int, int> map{
|
||||
stream,
|
||||
mr,
|
||||
static_cast<::cuda::std::size_t>(num_elements),
|
||||
load_factor,
|
||||
cudax::cuco::empty_key{empty_key},
|
||||
cudax::cuco::empty_value{empty_value}};
|
||||
|
||||
// With load_factor = 0.5 and 500 elements, capacity should be >= 1000
|
||||
REQUIRE(map.capacity() >= static_cast<::cuda::std::size_t>(num_elements / load_factor));
|
||||
}
|
||||
@@ -1,172 +0,0 @@
|
||||
//===----------------------------------------------------------------------===//
|
||||
//
|
||||
// Part of CUDA Experimental in CUDA C++ Core Libraries,
|
||||
// under the Apache License v2.0 with LLVM Exceptions.
|
||||
// See https://llvm.org/LICENSE.txt for license information.
|
||||
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
||||
// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES.
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
// Temporary nvcc workaround __host__ __device__ dtor conflict in cuda::buffer
|
||||
#if defined(__CUDACC__)
|
||||
# pragma nv_diag_suppress 20011
|
||||
#endif // defined(__CUDACC__)
|
||||
|
||||
#include <thrust/execution_policy.h>
|
||||
#include <thrust/logical.h>
|
||||
|
||||
#include <cuda/buffer>
|
||||
#include <cuda/iterator>
|
||||
#include <cuda/memory_pool>
|
||||
#include <cuda/std/cstddef>
|
||||
#include <cuda/std/cstdint>
|
||||
#include <cuda/std/type_traits>
|
||||
#include <cuda/stream>
|
||||
|
||||
#include <cuda/experimental/__cuco/capacity.cuh>
|
||||
#include <cuda/experimental/__cuco/fixed_capacity_map.cuh>
|
||||
|
||||
#include <testing.cuh>
|
||||
|
||||
namespace cudax = cuda::experimental;
|
||||
|
||||
template <int N>
|
||||
using int_c = ::cuda::std::integral_constant<int, N>;
|
||||
|
||||
using key_types = c2h::type_list<::cuda::std::int32_t, ::cuda::std::int64_t>;
|
||||
using cg_sizes = c2h::type_list<int_c<1>, int_c<2>>;
|
||||
using bucket_sizes = c2h::type_list<int_c<1>, int_c<2>>;
|
||||
using probing_kinds = c2h::type_list<int_c<0>, int_c<1>>; // 0 = linear probing, 1 = double hashing
|
||||
|
||||
// Payloads are offset from their key so a bug that returns the key instead of the mapped value is caught.
|
||||
constexpr int payload_offset = 7;
|
||||
|
||||
template <class Pair>
|
||||
struct iota_pair
|
||||
{
|
||||
__host__ __device__ Pair operator()(typename Pair::first_type i) const noexcept
|
||||
{
|
||||
return Pair{i, static_cast<typename Pair::second_type>(i + payload_offset)};
|
||||
}
|
||||
};
|
||||
|
||||
// Present keys [0, num_keys) find their payload (key + payload_offset) and absent keys
|
||||
// [num_keys, ...) find the empty value sentinel.
|
||||
template <class Key>
|
||||
struct match_found
|
||||
{
|
||||
const Key* found;
|
||||
int num_keys;
|
||||
Key sentinel;
|
||||
|
||||
__device__ bool operator()(int i) const noexcept
|
||||
{
|
||||
return (i < num_keys) ? (found[i] == static_cast<Key>(i) + payload_offset) : (found[i] == sentinel);
|
||||
}
|
||||
};
|
||||
|
||||
template <class Key>
|
||||
struct is_not_sentinel
|
||||
{
|
||||
Key sentinel;
|
||||
|
||||
__device__ bool operator()(Key value) const noexcept
|
||||
{
|
||||
return value != sentinel;
|
||||
}
|
||||
};
|
||||
|
||||
struct is_even
|
||||
{
|
||||
__device__ bool operator()(int i) const noexcept
|
||||
{
|
||||
return (i % 2) == 0;
|
||||
}
|
||||
};
|
||||
|
||||
// find_if queries only even keys; odd positions resolve to the empty value sentinel
|
||||
template <class Key>
|
||||
struct match_find_if
|
||||
{
|
||||
const Key* found;
|
||||
Key sentinel;
|
||||
|
||||
__device__ bool operator()(int i) const noexcept
|
||||
{
|
||||
return ((i % 2) == 0) ? (found[i] == static_cast<Key>(i) + payload_offset) : (found[i] == sentinel);
|
||||
}
|
||||
};
|
||||
|
||||
C2H_TEST("fixed_capacity_map find", "[container]", key_types, cg_sizes, bucket_sizes, probing_kinds)
|
||||
{
|
||||
using key_type = c2h::get<0, TestType>;
|
||||
[[maybe_unused]] constexpr int cg_size = c2h::get<1, TestType>::value;
|
||||
[[maybe_unused]] constexpr int bucket_size = c2h::get<2, TestType>::value;
|
||||
[[maybe_unused]] constexpr int probing = c2h::get<3, TestType>::value;
|
||||
|
||||
using hasher = cudax::cuco::hash<key_type>;
|
||||
using probing_type =
|
||||
::cuda::std::conditional_t<probing == 0,
|
||||
cudax::cuco::linear_probing<cg_size, hasher>,
|
||||
cudax::cuco::double_hashing<cg_size, hasher>>;
|
||||
using map_type = cudax::cuco::fixed_capacity_map<
|
||||
key_type,
|
||||
key_type,
|
||||
::cuda::std::dynamic_extent,
|
||||
::cuda::thread_scope_device,
|
||||
::cuda::std::equal_to<key_type>,
|
||||
probing_type,
|
||||
bucket_size>;
|
||||
using value_type = typename map_type::value_type;
|
||||
|
||||
constexpr int num_keys = 400;
|
||||
constexpr key_type sentinel = key_type{-1};
|
||||
|
||||
::cuda::stream stream{::cuda::device_ref{0}};
|
||||
auto mr = ::cuda::device_default_memory_pool(::cuda::device_ref{0});
|
||||
|
||||
map_type map{stream,
|
||||
mr,
|
||||
static_cast<::cuda::std::size_t>(num_keys * 2),
|
||||
cudax::cuco::empty_key{sentinel},
|
||||
cudax::cuco::empty_value{sentinel}};
|
||||
|
||||
auto pairs = cuda::transform_iterator(cuda::counting_iterator<key_type>{0}, iota_pair<value_type>{});
|
||||
map.insert(stream, pairs, pairs + num_keys);
|
||||
|
||||
// Find present keys [0, num_keys) and absent keys [num_keys, 2 * num_keys)
|
||||
auto found = ::cuda::make_buffer<key_type>(stream, mr, 2 * num_keys, key_type{0});
|
||||
map.find(stream, cuda::counting_iterator<key_type>{0}, cuda::counting_iterator<key_type>{2 * num_keys}, found.begin());
|
||||
|
||||
REQUIRE(::thrust::all_of(
|
||||
::thrust::cuda::par.on(stream.get()),
|
||||
cuda::counting_iterator<int>{0},
|
||||
cuda::counting_iterator<int>{2 * num_keys},
|
||||
match_found<key_type>{found.data(), num_keys, sentinel}));
|
||||
|
||||
// find_if only queries even keys; odd positions resolve to the empty value sentinel
|
||||
auto found_if = ::cuda::make_buffer<key_type>(stream, mr, num_keys, key_type{0});
|
||||
map.find_if(stream,
|
||||
cuda::counting_iterator<key_type>{0},
|
||||
cuda::counting_iterator<key_type>{num_keys},
|
||||
cuda::counting_iterator<int>{0},
|
||||
is_even{},
|
||||
found_if.begin());
|
||||
|
||||
REQUIRE(::thrust::all_of(
|
||||
::thrust::cuda::par.on(stream.get()),
|
||||
cuda::counting_iterator<int>{0},
|
||||
cuda::counting_iterator<int>{num_keys},
|
||||
match_find_if<key_type>{found_if.data(), sentinel}));
|
||||
|
||||
// After clear the map is empty, so every key resolves to the empty value sentinel
|
||||
map.clear(stream);
|
||||
auto cleared = ::cuda::make_buffer<key_type>(stream, mr, num_keys, key_type{0});
|
||||
map.find(stream, cuda::counting_iterator<key_type>{0}, cuda::counting_iterator<key_type>{num_keys}, cleared.begin());
|
||||
REQUIRE(::thrust::none_of(
|
||||
::thrust::cuda::par.on(stream.get()),
|
||||
cleared.data(),
|
||||
cleared.data() + num_keys,
|
||||
is_not_sentinel<key_type>{sentinel}));
|
||||
}
|
||||
@@ -1,150 +0,0 @@
|
||||
//===----------------------------------------------------------------------===//
|
||||
//
|
||||
// Part of CUDA Experimental in CUDA C++ Core Libraries,
|
||||
// under the Apache License v2.0 with LLVM Exceptions.
|
||||
// See https://llvm.org/LICENSE.txt for license information.
|
||||
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
||||
// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES.
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
// Temporary nvcc workaround __host__ __device__ dtor conflict in cuda::buffer
|
||||
#if defined(__CUDACC__)
|
||||
# pragma nv_diag_suppress 20011
|
||||
#endif
|
||||
|
||||
#include <thrust/execution_policy.h>
|
||||
#include <thrust/logical.h>
|
||||
|
||||
#include <cuda/buffer>
|
||||
#include <cuda/iterator>
|
||||
#include <cuda/memory_pool>
|
||||
#include <cuda/std/__bit/has_single_bit.h>
|
||||
#include <cuda/std/cstddef>
|
||||
#include <cuda/std/cstdint>
|
||||
#include <cuda/std/limits>
|
||||
#include <cuda/std/type_traits>
|
||||
#include <cuda/stream>
|
||||
|
||||
#include <cuda/experimental/__cuco/capacity.cuh>
|
||||
#include <cuda/experimental/__cuco/fixed_capacity_map.cuh>
|
||||
|
||||
#include <testing.cuh>
|
||||
|
||||
namespace cudax = cuda::experimental;
|
||||
|
||||
template <int _N>
|
||||
using _int_c = ::cuda::std::integral_constant<int, _N>;
|
||||
|
||||
using key_types =
|
||||
c2h::type_list<::cuda::std::uint8_t, ::cuda::std::uint16_t, ::cuda::std::int32_t, ::cuda::std::int64_t>;
|
||||
using cg_sizes = c2h::type_list<_int_c<1>, _int_c<2>>;
|
||||
using bucket_sizes = c2h::type_list<_int_c<1>, _int_c<2>>;
|
||||
using probing_kinds = c2h::type_list<_int_c<0>, _int_c<1>>; // 0 = linear probing, 1 = double hashing
|
||||
|
||||
template <class _Pair>
|
||||
struct iota_pair
|
||||
{
|
||||
__host__ __device__ _Pair operator()(typename _Pair::first_type __i) const noexcept
|
||||
{
|
||||
return _Pair{__i, __i};
|
||||
}
|
||||
};
|
||||
|
||||
// Present keys [0, num_keys) are found, absent keys [num_keys, ...) are not
|
||||
struct match_expected
|
||||
{
|
||||
const int* found;
|
||||
int num_keys;
|
||||
|
||||
__device__ bool operator()(int i) const noexcept
|
||||
{
|
||||
return static_cast<bool>(found[i]) == (i < num_keys);
|
||||
}
|
||||
};
|
||||
|
||||
struct is_nonzero
|
||||
{
|
||||
__device__ bool operator()(int v) const noexcept
|
||||
{
|
||||
return v != 0;
|
||||
}
|
||||
};
|
||||
|
||||
C2H_TEST("fixed_capacity_map insert and contains", "[container]", key_types, cg_sizes, bucket_sizes, probing_kinds)
|
||||
{
|
||||
using key_type = c2h::get<0, TestType>;
|
||||
[[maybe_unused]] constexpr int cg_size = c2h::get<1, TestType>::value;
|
||||
[[maybe_unused]] constexpr int bucket_size = c2h::get<2, TestType>::value;
|
||||
[[maybe_unused]] constexpr int probing = c2h::get<3, TestType>::value;
|
||||
|
||||
using hasher = cudax::cuco::hash<key_type>;
|
||||
using probing_type =
|
||||
::cuda::std::conditional_t<probing == 0,
|
||||
cudax::cuco::linear_probing<cg_size, hasher>,
|
||||
cudax::cuco::double_hashing<cg_size, hasher>>;
|
||||
using map_type = cudax::cuco::fixed_capacity_map<
|
||||
key_type,
|
||||
key_type,
|
||||
::cuda::std::dynamic_extent,
|
||||
::cuda::thread_scope_device,
|
||||
::cuda::std::equal_to<key_type>,
|
||||
probing_type,
|
||||
bucket_size>;
|
||||
using value_type = typename map_type::value_type;
|
||||
|
||||
constexpr int num_keys = (::cuda::std::numeric_limits<key_type>::max() > 800) ? 400 : 100;
|
||||
|
||||
::cuda::stream stream{::cuda::device_ref{0}};
|
||||
auto mr = ::cuda::device_default_memory_pool(::cuda::device_ref{0});
|
||||
|
||||
map_type map{stream,
|
||||
mr,
|
||||
static_cast<::cuda::std::size_t>(num_keys * 2),
|
||||
cudax::cuco::empty_key{static_cast<key_type>(-1)},
|
||||
cudax::cuco::empty_value{static_cast<key_type>(-1)}};
|
||||
|
||||
auto __pairs = cuda::transform_iterator(cuda::counting_iterator<key_type>{0}, iota_pair<value_type>{});
|
||||
map.insert(stream, __pairs, __pairs + num_keys);
|
||||
|
||||
// Query present keys [0, num_keys) and absent keys [num_keys, 2 * num_keys)
|
||||
auto found = ::cuda::make_buffer<int>(stream, mr, 2 * num_keys, 0);
|
||||
map.contains(
|
||||
stream, cuda::counting_iterator<key_type>{0}, cuda::counting_iterator<key_type>{2 * num_keys}, found.begin());
|
||||
|
||||
REQUIRE(::thrust::all_of(
|
||||
::thrust::cuda::par.on(stream.get()),
|
||||
cuda::counting_iterator<int>{0},
|
||||
cuda::counting_iterator<int>{2 * num_keys},
|
||||
match_expected{found.data(), num_keys}));
|
||||
|
||||
// After clear the map is empty, so none of the previously inserted keys are found
|
||||
map.clear(stream);
|
||||
auto cleared = ::cuda::make_buffer<int>(stream, mr, num_keys, 1);
|
||||
map.contains(
|
||||
stream, cuda::counting_iterator<key_type>{0}, cuda::counting_iterator<key_type>{num_keys}, cleared.begin());
|
||||
REQUIRE(
|
||||
::thrust::none_of(::thrust::cuda::par.on(stream.get()), cleared.data(), cleared.data() + num_keys, is_nonzero{}));
|
||||
}
|
||||
|
||||
template <class _Key, class _Tp>
|
||||
using __map_of = cudax::cuco::fixed_capacity_map<
|
||||
_Key,
|
||||
_Tp,
|
||||
::cuda::std::dynamic_extent,
|
||||
::cuda::thread_scope_device,
|
||||
::cuda::std::equal_to<_Key>,
|
||||
cudax::cuco::linear_probing<1, cudax::cuco::hash<_Key>>,
|
||||
1>;
|
||||
|
||||
C2H_TEST("fixed_capacity_map key and slot size constraint", "[container]")
|
||||
{
|
||||
static_assert(sizeof(typename __map_of<::cuda::std::uint8_t, ::cuda::std::uint8_t>::value_type) == 2,
|
||||
"<uint8_t, uint8_t> is a valid 2-byte slot");
|
||||
static_assert(sizeof(typename __map_of<::cuda::std::uint16_t, ::cuda::std::uint16_t>::value_type) == 4,
|
||||
"<uint16_t, uint16_t> is a valid 4-byte slot");
|
||||
static_assert(sizeof(typename __map_of<::cuda::std::uint32_t, ::cuda::std::uint32_t>::value_type) == 8,
|
||||
"<uint32_t, uint32_t> is a valid 8-byte slot");
|
||||
static_assert(sizeof(typename __map_of<::cuda::std::uint8_t, ::cuda::std::uint32_t>::value_type) == 8,
|
||||
"a mismatched <uint8_t, uint32_t> slot is a valid 8-byte slot");
|
||||
}
|
||||
@@ -1,80 +0,0 @@
|
||||
//===----------------------------------------------------------------------===//
|
||||
//
|
||||
// Part of CUDA Experimental in CUDA C++ Core Libraries,
|
||||
// under the Apache License v2.0 with LLVM Exceptions.
|
||||
// See https://llvm.org/LICENSE.txt for license information.
|
||||
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
||||
// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES.
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
// Temporary nvcc workaround __host__ __device__ dtor conflict in cuda::buffer
|
||||
#if defined(__CUDACC__)
|
||||
# pragma nv_diag_suppress 20011
|
||||
#endif
|
||||
|
||||
#include <thrust/execution_policy.h>
|
||||
#include <thrust/logical.h>
|
||||
|
||||
#include <cuda/buffer>
|
||||
#include <cuda/iterator>
|
||||
#include <cuda/memory_pool>
|
||||
#include <cuda/std/cstddef>
|
||||
#include <cuda/stream>
|
||||
|
||||
#include <cuda/experimental/__cuco/capacity.cuh>
|
||||
#include <cuda/experimental/__cuco/fixed_capacity_map.cuh>
|
||||
|
||||
#include <testing.cuh>
|
||||
|
||||
namespace cudax = cuda::experimental;
|
||||
|
||||
constexpr int empty_key = -1;
|
||||
constexpr int empty_value = -1;
|
||||
|
||||
// Constructing a map with an erased-key sentinel must keep insert and contains correct (no key ever
|
||||
// collides with the empty or erased sentinels).
|
||||
template <class _Pair>
|
||||
struct iota_pair
|
||||
{
|
||||
__host__ __device__ _Pair operator()(typename _Pair::first_type __i) const noexcept
|
||||
{
|
||||
return _Pair{__i, __i};
|
||||
}
|
||||
};
|
||||
|
||||
struct is_nonzero
|
||||
{
|
||||
__device__ bool operator()(int v) const noexcept
|
||||
{
|
||||
return v != 0;
|
||||
}
|
||||
};
|
||||
|
||||
C2H_TEST("fixed_capacity_map — empty and erased key sentinels", "[sentinel]")
|
||||
{
|
||||
constexpr int erased_sentinel = -2;
|
||||
constexpr int num_keys = 256;
|
||||
|
||||
using probing = cudax::cuco::linear_probing<1, cudax::cuco::hash<int>>;
|
||||
[[maybe_unused]] constexpr int bucket = 1;
|
||||
[[maybe_unused]] constexpr ::cuda::std::size_t capacity =
|
||||
cudax::cuco::make_valid_capacity<probing, bucket>(::cuda::std::size_t{num_keys} * 2);
|
||||
using map_type = cudax::cuco::fixed_capacity_map<int, int, capacity>;
|
||||
|
||||
::cuda::stream stream{::cuda::device_ref{0}};
|
||||
auto mr = ::cuda::device_default_memory_pool(::cuda::device_ref{0});
|
||||
|
||||
map_type map{stream,
|
||||
mr,
|
||||
cudax::cuco::empty_key{empty_key},
|
||||
cudax::cuco::empty_value{empty_value},
|
||||
cudax::cuco::erased_key{erased_sentinel}};
|
||||
|
||||
auto __pairs = cuda::transform_iterator(cuda::counting_iterator<int>{0}, iota_pair<::cuda::std::pair<int, int>>{});
|
||||
map.insert(stream, __pairs, __pairs + num_keys);
|
||||
|
||||
auto found = ::cuda::make_buffer<int>(stream, mr, num_keys, 0);
|
||||
map.contains(stream, cuda::counting_iterator<int>{0}, cuda::counting_iterator<int>{num_keys}, found.begin());
|
||||
REQUIRE(::thrust::all_of(::thrust::cuda::par.on(stream.get()), found.data(), found.data() + num_keys, is_nonzero{}));
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
//===----------------------------------------------------------------------===//
|
||||
//
|
||||
// Part of CUDA Experimental in CUDA C++ Core Libraries,
|
||||
// under the Apache License v2.0 with LLVM Exceptions.
|
||||
// See https://llvm.org/LICENSE.txt for license information.
|
||||
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
||||
// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES.
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
// Static error check: a key type whose size is not a power of two is rejected. `char3` is 3 bytes,
|
||||
// which falls between the supported key widths (1, 2, 4, 8 bytes).
|
||||
|
||||
#include <cuda/std/cstdint>
|
||||
|
||||
#include <cuda/experimental/__cuco/fixed_capacity_map.cuh>
|
||||
|
||||
namespace cudax = cuda::experimental;
|
||||
|
||||
int main()
|
||||
{
|
||||
using map_t = cudax::cuco::fixed_capacity_map<char3, ::cuda::std::uint8_t>;
|
||||
// expected-error {{"key_type size must be a power of two"}}
|
||||
static_assert(sizeof(typename map_t::ref_type) > 0);
|
||||
return 0;
|
||||
}
|
||||
@@ -1,137 +0,0 @@
|
||||
//===----------------------------------------------------------------------===//
|
||||
//
|
||||
// Part of CUDA Experimental in CUDA C++ Core Libraries,
|
||||
// under the Apache License v2.0 with LLVM Exceptions.
|
||||
// See https://llvm.org/LICENSE.txt for license information.
|
||||
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
||||
// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES.
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
// Inserts and lookups must stay correct when the slot storage is under-aligned for the packed
|
||||
// atomic CAS, which forces the insert path onto the non-packed fallback.
|
||||
|
||||
// Temporary nvcc workaround __host__ __device__ dtor conflict in cuda::buffer
|
||||
#if defined(__CUDACC__)
|
||||
# pragma nv_diag_suppress 20011
|
||||
#endif
|
||||
|
||||
#include <cuda/__memory/align_up.h>
|
||||
#include <cuda/std/cstddef>
|
||||
#include <cuda/std/cstdint>
|
||||
#include <cuda/std/functional>
|
||||
|
||||
#include <cuda/experimental/__cuco/capacity.cuh>
|
||||
#include <cuda/experimental/__cuco/fixed_capacity_map.cuh>
|
||||
|
||||
#include <testing.cuh>
|
||||
|
||||
namespace cudax = cuda::experimental;
|
||||
|
||||
template <class ValueType>
|
||||
__global__ void fill_sentinel_kernel(ValueType* slots, int cap, ValueType sentinel)
|
||||
{
|
||||
const int i = static_cast<int>(blockIdx.x * blockDim.x + threadIdx.x);
|
||||
if (i < cap)
|
||||
{
|
||||
slots[i] = sentinel;
|
||||
}
|
||||
}
|
||||
|
||||
template <class RefType, class Key>
|
||||
__global__ void insert_kernel(RefType ref, int num_keys)
|
||||
{
|
||||
const int i = static_cast<int>(blockIdx.x * blockDim.x + threadIdx.x);
|
||||
if (i < num_keys)
|
||||
{
|
||||
[[maybe_unused]] const bool inserted =
|
||||
ref.insert(typename RefType::value_type{static_cast<Key>(i), static_cast<Key>(i)});
|
||||
}
|
||||
}
|
||||
|
||||
template <class RefType, class Key>
|
||||
__global__ void contains_kernel(RefType ref, int num_probes, int* out)
|
||||
{
|
||||
const int i = static_cast<int>(blockIdx.x * blockDim.x + threadIdx.x);
|
||||
if (i < num_probes)
|
||||
{
|
||||
out[i] = ref.contains(static_cast<Key>(i)) ? 1 : 0;
|
||||
}
|
||||
}
|
||||
|
||||
template <class Key, class Mapped>
|
||||
void run_misaligned_external_storage()
|
||||
{
|
||||
using probing_type = cudax::cuco::linear_probing<1, cudax::cuco::hash<Key>>;
|
||||
constexpr int bucket_size = 1;
|
||||
using map_type = cudax::cuco::fixed_capacity_map<
|
||||
Key,
|
||||
Mapped,
|
||||
::cuda::std::dynamic_extent,
|
||||
::cuda::thread_scope_device,
|
||||
::cuda::std::equal_to<Key>,
|
||||
probing_type,
|
||||
bucket_size>;
|
||||
using ref_type = typename map_type::ref_type;
|
||||
using value_type = typename map_type::value_type;
|
||||
using span_type = typename ref_type::storage_span_type;
|
||||
|
||||
constexpr int num_keys = 200;
|
||||
const auto capacity =
|
||||
cudax::cuco::make_valid_capacity<probing_type, bucket_size>(static_cast<::cuda::std::size_t>(num_keys) * 2);
|
||||
|
||||
const Key empty_k = static_cast<Key>(-1);
|
||||
const Mapped empty_v = static_cast<Mapped>(-1);
|
||||
|
||||
const ::cuda::std::size_t nbytes = (capacity + 2) * sizeof(value_type);
|
||||
void* raw = nullptr;
|
||||
REQUIRE(cudaMalloc(&raw, nbytes) == cudaSuccess);
|
||||
|
||||
auto* const aligned_raw = ::cuda::align_up(static_cast<::cuda::std::byte*>(raw), sizeof(value_type));
|
||||
auto* const slots = reinterpret_cast<value_type*>(aligned_raw + alignof(value_type));
|
||||
const auto slots_addr = reinterpret_cast<::cuda::std::uintptr_t>(slots);
|
||||
REQUIRE(slots_addr % alignof(value_type) == 0);
|
||||
REQUIRE(slots_addr % sizeof(value_type) != 0);
|
||||
|
||||
constexpr int block = 128;
|
||||
|
||||
const int fill_grid = static_cast<int>((capacity + block - 1) / block);
|
||||
fill_sentinel_kernel<value_type>
|
||||
<<<fill_grid, block>>>(slots, static_cast<int>(capacity), value_type{empty_k, empty_v});
|
||||
REQUIRE(cudaGetLastError() == cudaSuccess);
|
||||
REQUIRE(cudaDeviceSynchronize() == cudaSuccess);
|
||||
|
||||
ref_type ref{cudax::cuco::empty_key<Key>{empty_k},
|
||||
cudax::cuco::empty_value<Mapped>{empty_v},
|
||||
::cuda::std::equal_to<Key>{},
|
||||
probing_type{},
|
||||
span_type{slots, capacity}};
|
||||
|
||||
insert_kernel<ref_type, Key><<<(num_keys + block - 1) / block, block>>>(ref, num_keys);
|
||||
REQUIRE(cudaGetLastError() == cudaSuccess);
|
||||
REQUIRE(cudaDeviceSynchronize() == cudaSuccess);
|
||||
|
||||
constexpr int num_probes = 2 * num_keys;
|
||||
int* d_out = nullptr;
|
||||
REQUIRE(cudaMalloc(&d_out, sizeof(int) * num_probes) == cudaSuccess);
|
||||
contains_kernel<ref_type, Key><<<(num_probes + block - 1) / block, block>>>(ref, num_probes, d_out);
|
||||
REQUIRE(cudaGetLastError() == cudaSuccess);
|
||||
|
||||
int h_out[num_probes];
|
||||
REQUIRE(cudaMemcpy(h_out, d_out, sizeof(int) * num_probes, cudaMemcpyDeviceToHost) == cudaSuccess);
|
||||
REQUIRE(cudaDeviceSynchronize() == cudaSuccess);
|
||||
|
||||
for (int i = 0; i < num_probes; ++i)
|
||||
{
|
||||
REQUIRE(static_cast<bool>(h_out[i]) == (i < num_keys));
|
||||
}
|
||||
|
||||
REQUIRE(cudaFree(d_out) == cudaSuccess);
|
||||
REQUIRE(cudaFree(raw) == cudaSuccess);
|
||||
}
|
||||
|
||||
C2H_TEST("fixed_capacity_map insert and contains over misaligned external storage", "[container]")
|
||||
{
|
||||
run_misaligned_external_storage<::cuda::std::int32_t, ::cuda::std::int32_t>();
|
||||
run_misaligned_external_storage<::cuda::std::uint16_t, ::cuda::std::uint16_t>();
|
||||
}
|
||||
@@ -1,102 +0,0 @@
|
||||
//===----------------------------------------------------------------------===//
|
||||
//
|
||||
// Part of CUDA Experimental in CUDA C++ Core Libraries,
|
||||
// under the Apache License v2.0 with LLVM Exceptions.
|
||||
// See https://llvm.org/LICENSE.txt for license information.
|
||||
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
||||
// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES.
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
// Temporary nvcc workaround __host__ __device__ dtor conflict in cuda::buffer
|
||||
#if defined(__CUDACC__)
|
||||
# pragma nv_diag_suppress 20011
|
||||
#endif
|
||||
|
||||
#include <thrust/execution_policy.h>
|
||||
#include <thrust/logical.h>
|
||||
|
||||
#include <cuda/buffer>
|
||||
#include <cuda/iterator>
|
||||
#include <cuda/memory>
|
||||
#include <cuda/memory_pool>
|
||||
#include <cuda/std/cstddef>
|
||||
#include <cuda/stream>
|
||||
|
||||
#include <cuda/experimental/__cuco/capacity.cuh>
|
||||
#include <cuda/experimental/__cuco/fixed_capacity_map.cuh>
|
||||
|
||||
#include <testing.cuh>
|
||||
|
||||
namespace cudax = cuda::experimental;
|
||||
|
||||
constexpr int empty_key = -1;
|
||||
constexpr int empty_value = -1;
|
||||
|
||||
// A static-capacity map with cg_size 1 so the test can use scalar device inserts.
|
||||
using probing = cudax::cuco::linear_probing<1, cudax::cuco::hash<int>>;
|
||||
inline constexpr int bucket = 1;
|
||||
inline constexpr ::cuda::std::size_t static_capacity =
|
||||
cudax::cuco::make_valid_capacity<probing, bucket>(::cuda::std::size_t{512});
|
||||
using fixed_capacity_map_512_type = cudax::cuco::
|
||||
fixed_capacity_map<int, int, static_capacity, ::cuda::thread_scope_device, ::cuda::std::equal_to<int>, probing>;
|
||||
|
||||
template <class Pair>
|
||||
struct iota_pair
|
||||
{
|
||||
__host__ __device__ Pair operator()(typename Pair::first_type key) const noexcept
|
||||
{
|
||||
return Pair{key, key};
|
||||
}
|
||||
};
|
||||
|
||||
struct is_nonzero
|
||||
{
|
||||
__device__ bool operator()(int v) const noexcept
|
||||
{
|
||||
return v != 0;
|
||||
}
|
||||
};
|
||||
|
||||
// Demonstrates compile-time __shared__ sizing via ref_type::capacity_v.
|
||||
template <class PairIt>
|
||||
__global__ void insert_shmem_kernel(fixed_capacity_map_512_type::ref_type global_ref, PairIt pairs, int num_keys)
|
||||
{
|
||||
using ref_t = fixed_capacity_map_512_type::ref_type;
|
||||
static_assert(ref_t::capacity_v != ::cuda::std::dynamic_extent,
|
||||
"capacity_v must be a compile-time constant for static extents");
|
||||
|
||||
__shared__ ::cuda::__uninitialized_array<ref_t::value_type, ref_t::capacity_v> smem;
|
||||
|
||||
const auto idx = static_cast<int>(blockIdx.x) * blockDim.x + threadIdx.x;
|
||||
smem[threadIdx.x] = (idx < num_keys) ? pairs[idx] : ref_t::value_type{};
|
||||
__syncthreads();
|
||||
if (idx < num_keys)
|
||||
{
|
||||
global_ref.insert(smem[threadIdx.x]);
|
||||
}
|
||||
}
|
||||
|
||||
C2H_TEST("fixed_capacity_map static extent — shared memory sizing via capacity_v", "[shmem][static]")
|
||||
{
|
||||
constexpr int num_keys = 64;
|
||||
|
||||
::cuda::stream stream{::cuda::device_ref{0}};
|
||||
auto mr = ::cuda::device_default_memory_pool(::cuda::device_ref{0});
|
||||
|
||||
fixed_capacity_map_512_type map{stream, mr, cudax::cuco::empty_key{empty_key}, cudax::cuco::empty_value{empty_value}};
|
||||
|
||||
const int block_size = 128;
|
||||
const int grid_size = (num_keys + block_size - 1) / block_size;
|
||||
|
||||
insert_shmem_kernel<<<grid_size, block_size, 0, stream.get()>>>(
|
||||
map.ref(),
|
||||
cuda::transform_iterator(cuda::counting_iterator<int>{0}, iota_pair<fixed_capacity_map_512_type::value_type>{}),
|
||||
num_keys);
|
||||
REQUIRE(cudaGetLastError() == cudaSuccess);
|
||||
|
||||
// Verify the insertions actually landed in the global map
|
||||
auto found = ::cuda::make_buffer<int>(stream, mr, num_keys, 0);
|
||||
map.contains(stream, cuda::counting_iterator<int>{0}, cuda::counting_iterator<int>{num_keys}, found.begin());
|
||||
REQUIRE(::thrust::all_of(::thrust::cuda::par.on(stream.get()), found.data(), found.data() + num_keys, is_nonzero{}));
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
//===----------------------------------------------------------------------===//
|
||||
//
|
||||
// Part of CUDA Experimental in CUDA C++ Core Libraries,
|
||||
// under the Apache License v2.0 with LLVM Exceptions.
|
||||
// See https://llvm.org/LICENSE.txt for license information.
|
||||
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
||||
// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES.
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
// Static error check: a slot (key/payload pair) whose size is not a power of two is rejected. A
|
||||
// 1-byte key with a 5-byte payload forms a 6-byte slot, which the packed atomic update cannot
|
||||
// address even though the key size alone is a valid power of two.
|
||||
|
||||
#include <cuda/std/cstdint>
|
||||
|
||||
#include <cuda/experimental/__cuco/fixed_capacity_map.cuh>
|
||||
|
||||
namespace cudax = cuda::experimental;
|
||||
|
||||
struct byte5
|
||||
{
|
||||
unsigned char a, b, c, d, e;
|
||||
};
|
||||
|
||||
int main()
|
||||
{
|
||||
using map_t = cudax::cuco::fixed_capacity_map<::cuda::std::uint8_t, byte5>;
|
||||
// expected-error {{"value_type size must be a power of two"}}
|
||||
static_assert(sizeof(typename map_t::ref_type) > 0);
|
||||
return 0;
|
||||
}
|
||||
@@ -1,341 +0,0 @@
|
||||
//===----------------------------------------------------------------------===//
|
||||
//
|
||||
// Part of CUDA Experimental in CUDA C++ Core Libraries,
|
||||
// under the Apache License v2.0 with LLVM Exceptions.
|
||||
// See https://llvm.org/LICENSE.txt for license information.
|
||||
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
||||
// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES.
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
#include <thrust/execution_policy.h>
|
||||
#include <thrust/sequence.h>
|
||||
|
||||
#include <cuda/buffer>
|
||||
#include <cuda/iterator>
|
||||
#include <cuda/memory_pool>
|
||||
#include <cuda/std/cmath>
|
||||
#include <cuda/std/cstddef>
|
||||
#include <cuda/std/span>
|
||||
#include <cuda/std/type_traits>
|
||||
#include <cuda/stream>
|
||||
|
||||
#include <cuda/experimental/__cuco/hash_functions.cuh>
|
||||
#include <cuda/experimental/__cuco/hyperloglog.cuh>
|
||||
#include <cuda/experimental/__cuco/hyperloglog_ref.cuh>
|
||||
|
||||
#include <cooperative_groups.h>
|
||||
#include <testing.cuh>
|
||||
|
||||
#include <c2h/catch2_test_helper.h>
|
||||
#include <catch2/matchers/catch_matchers_floating_point.hpp>
|
||||
|
||||
namespace cudax = cuda::experimental;
|
||||
|
||||
template <typename Ref, typename InputIt, typename OutputIt>
|
||||
__global__ void estimate_kernel(typename Ref::sketch_size_kb sketch_size_kb, InputIt in, size_t n, OutputIt out)
|
||||
{
|
||||
extern __shared__ cuda::std::byte local_sketch[];
|
||||
|
||||
const auto block = cooperative_groups::this_thread_block();
|
||||
|
||||
// only a single block computes the estimate
|
||||
if (block.group_index().x == 0)
|
||||
{
|
||||
Ref estimator(cuda::std::span(local_sketch, Ref::sketch_bytes(sketch_size_kb)));
|
||||
|
||||
estimator.clear(block);
|
||||
block.sync();
|
||||
|
||||
for (int i = static_cast<int>(block.thread_rank()); i < n; i += static_cast<int>(block.num_threads()))
|
||||
{
|
||||
estimator.add(*(in + i));
|
||||
}
|
||||
block.sync();
|
||||
static_assert(cuda::std::is_same_v<decltype(estimator.estimate(block)), double>);
|
||||
const auto estimate = estimator.estimate(block);
|
||||
if (block.thread_rank() == 0)
|
||||
{
|
||||
*out = estimate;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <typename Ref>
|
||||
__global__ void merge_kernel(Ref destination, const Ref source)
|
||||
{
|
||||
const auto block = cooperative_groups::this_thread_block();
|
||||
destination.merge(block, source);
|
||||
}
|
||||
|
||||
using test_types = c2h::type_list<int32_t, int64_t>;
|
||||
|
||||
// Maps index i to i / repeats, yielding `repeats` duplicates of each value
|
||||
struct scaled_index
|
||||
{
|
||||
std::size_t repeats;
|
||||
|
||||
__device__ int operator()(std::size_t i) const noexcept
|
||||
{
|
||||
return static_cast<int>(i / repeats);
|
||||
}
|
||||
};
|
||||
|
||||
C2H_TEST("HyperLogLog device ref", "[hyperloglog]", test_types)
|
||||
{
|
||||
using T = c2h::get<0, TestType>;
|
||||
using estimator_type = cudax::cuco::hyperloglog<T>;
|
||||
|
||||
// Test parameters
|
||||
const std::size_t num_items_pow2 = GENERATE(25, 26, 28);
|
||||
const int hll_precision = GENERATE(8, 10, 12, 13);
|
||||
const typename estimator_type::sketch_size_kb sketch_size_kb(4.0 * (1ull << hll_precision) / 1024.0);
|
||||
const std::size_t num_items = 1ull << num_items_pow2;
|
||||
|
||||
CAPTURE(num_items, hll_precision, sketch_size_kb);
|
||||
|
||||
::cuda::stream stream{::cuda::device_ref{0}};
|
||||
auto mr = ::cuda::device_default_memory_pool(::cuda::device_ref{0});
|
||||
|
||||
// Generate `num_items` distinct items
|
||||
auto items = ::cuda::make_buffer<T>(stream, mr, num_items, ::cuda::no_init);
|
||||
thrust::sequence(thrust::cuda::par_nosync.on(stream.get()), items.begin(), items.end(), T{0});
|
||||
|
||||
// Initialize the estimator
|
||||
estimator_type estimator{stream, mr, sketch_size_kb};
|
||||
STATIC_REQUIRE(cuda::std::is_same_v<decltype(estimator.estimate(stream)), double>);
|
||||
STATIC_REQUIRE(cuda::std::is_same_v<decltype(estimator.ref().estimate(stream)), double>);
|
||||
|
||||
// Add all items to the estimator
|
||||
estimator.add(stream, items.begin(), items.end());
|
||||
|
||||
const auto host_estimate = estimator.estimate(stream);
|
||||
|
||||
auto device_estimate = cuda::make_buffer<double>(stream, mr, 1, cuda::no_init);
|
||||
estimate_kernel<typename estimator_type::template ref_type<cuda::thread_scope_block>>
|
||||
<<<1, 512, estimator.sketch_bytes(), stream.get()>>>(
|
||||
sketch_size_kb, items.begin(), num_items, device_estimate.begin());
|
||||
REQUIRE(cudaGetLastError() == cudaSuccess);
|
||||
|
||||
double device_estimate_value{};
|
||||
REQUIRE_CUDART(cudaMemcpyAsync(
|
||||
&device_estimate_value, device_estimate.data(), sizeof(double), cudaMemcpyDeviceToHost, stream.get()));
|
||||
stream.sync();
|
||||
REQUIRE_THAT(device_estimate_value, Catch::Matchers::WithinRel(host_estimate, 1e-10));
|
||||
}
|
||||
|
||||
C2H_TEST("HyperLogLog device ref merge", "[hyperloglog]")
|
||||
{
|
||||
using T = int32_t;
|
||||
using estimator_type = cudax::cuco::hyperloglog<T>;
|
||||
|
||||
constexpr std::size_t num_items = 1 << 20;
|
||||
const estimator_type::precision precision{8};
|
||||
|
||||
::cuda::stream stream{::cuda::device_ref{0}};
|
||||
auto mr = ::cuda::device_default_memory_pool(::cuda::device_ref{0});
|
||||
|
||||
estimator_type source{stream, mr, precision};
|
||||
const auto first = ::cuda::counting_iterator<T>{0};
|
||||
source.add(stream, first, first + num_items);
|
||||
const auto source_estimate = source.estimate(stream);
|
||||
|
||||
estimator_type destination{stream, mr, precision};
|
||||
merge_kernel<<<1, 128, 0, stream.get()>>>(destination.ref(), source.ref());
|
||||
REQUIRE(cudaGetLastError() == cudaSuccess);
|
||||
|
||||
REQUIRE(destination.estimate(stream) == source_estimate);
|
||||
REQUIRE(source.estimate(stream) == source_estimate);
|
||||
}
|
||||
|
||||
C2H_TEST("HyperLogLog unique sequence", "[hyperloglog]", test_types)
|
||||
{
|
||||
using T = c2h::get<0, TestType>;
|
||||
using estimator_type = cudax::cuco::hyperloglog<T>;
|
||||
|
||||
const std::size_t num_items_pow2 = GENERATE(25, 26, 28);
|
||||
const int hll_precision = GENERATE(8, 10, 12, 13, 18);
|
||||
const typename estimator_type::sketch_size_kb sketch_size_kb(4.0 * (1ull << hll_precision) / 1024.0);
|
||||
const std::size_t num_items = 1ull << num_items_pow2;
|
||||
|
||||
CAPTURE(num_items, hll_precision, sketch_size_kb);
|
||||
|
||||
// This factor determines the error threshold for passing the test
|
||||
constexpr double tolerance_factor = 2.5;
|
||||
// RSD for a given precision is given by the following formula
|
||||
const double relative_standard_deviation = 1.04 / std::sqrt(static_cast<double>(1ull << hll_precision));
|
||||
|
||||
::cuda::stream stream{::cuda::device_ref{0}};
|
||||
auto mr = ::cuda::device_default_memory_pool(::cuda::device_ref{0});
|
||||
|
||||
// Generate `num_items` distinct items
|
||||
auto items = ::cuda::make_buffer<T>(stream, mr, num_items, ::cuda::no_init);
|
||||
thrust::sequence(thrust::cuda::par_nosync.on(stream.get()), items.begin(), items.end(), T{0});
|
||||
|
||||
// Initialize the estimator
|
||||
estimator_type estimator{stream, mr, sketch_size_kb};
|
||||
|
||||
REQUIRE(estimator.estimate(stream) == 0);
|
||||
|
||||
// Add all items to the estimator
|
||||
estimator.add(stream, items.begin(), items.end());
|
||||
|
||||
const auto estimate = estimator.estimate(stream);
|
||||
|
||||
// Adding the same items again should not affect the result
|
||||
estimator.add(stream, items.begin(), items.begin() + num_items / 2);
|
||||
REQUIRE(estimator.estimate(stream) == estimate);
|
||||
|
||||
// Adding the same items again (might use shared memory code path) should not affect the result
|
||||
auto* ptr = items.data();
|
||||
estimator.add(stream, ptr, ptr + num_items / 2);
|
||||
REQUIRE(estimator.estimate(stream) == estimate);
|
||||
|
||||
// Clearing the estimator should reset the estimate
|
||||
estimator.clear(stream);
|
||||
REQUIRE(estimator.estimate(stream) == 0);
|
||||
|
||||
const double relative_error = std::abs((static_cast<double>(estimate) / static_cast<double>(num_items)) - 1.0);
|
||||
|
||||
// Check if the error is acceptable
|
||||
REQUIRE(relative_error < tolerance_factor * relative_standard_deviation);
|
||||
}
|
||||
|
||||
//! @brief The following unit tests mimic Spark's unit tests which can be found here:
|
||||
//! https://github.com/apache/spark/blob/d10dbaa31a44878df5c7e144f111e18261346531/sql/catalyst/src/test/scala/org/apache/spark/sql/catalyst/expressions/aggregate/HyperLogLogPlusPlusSuite.scala
|
||||
//!
|
||||
|
||||
C2H_TEST("HyperLogLog Spark parity deterministic", "[hyperloglog]")
|
||||
{
|
||||
using T = int;
|
||||
using estimator_type = cudax::cuco::hyperloglog<T>;
|
||||
|
||||
constexpr std::size_t repeats = 10;
|
||||
// This factor determines the error threshold for passing the test
|
||||
constexpr double tolerance_factor = 3.0;
|
||||
const auto num_items = GENERATE(100, 500, 1000, 5000, 10000, 50000, 100000, 500000, 1000000);
|
||||
const auto standard_deviation = GENERATE(0.1, 0.05, 0.025, 0.01, 0.005, 0.0025);
|
||||
|
||||
const auto expected_hll_precision =
|
||||
std::max(static_cast<int32_t>(4),
|
||||
static_cast<int32_t>(std::ceil(2.0 * std::log(1.106 / standard_deviation) / std::log(2.0))));
|
||||
const auto expected_sketch_bytes = 4 * (1ull << expected_hll_precision);
|
||||
|
||||
CAPTURE(num_items, standard_deviation, expected_hll_precision, expected_sketch_bytes);
|
||||
|
||||
const estimator_type::standard_deviation sd(standard_deviation);
|
||||
const estimator_type::sketch_size_kb sb(expected_sketch_bytes / 1024.0);
|
||||
|
||||
// Validate sketch size calculation
|
||||
REQUIRE(estimator_type::sketch_bytes(sd) >= 64);
|
||||
REQUIRE(estimator_type::sketch_bytes(sd) == expected_sketch_bytes);
|
||||
REQUIRE(estimator_type::sketch_bytes(sd) == estimator_type::sketch_bytes(sb));
|
||||
|
||||
auto items_begin = cuda::transform_iterator(cuda::counting_iterator<std::size_t>{0}, scaled_index{repeats});
|
||||
|
||||
::cuda::stream stream{::cuda::device_ref{0}};
|
||||
auto mr = ::cuda::device_default_memory_pool(::cuda::device_ref{0});
|
||||
|
||||
estimator_type estimator{stream, mr, sd};
|
||||
|
||||
REQUIRE(estimator.estimate(stream) == 0);
|
||||
|
||||
// Add all items to the estimator
|
||||
estimator.add(stream, items_begin, items_begin + num_items);
|
||||
|
||||
// Spark rounds the floating-point estimate to the nearest integer with Math.round.
|
||||
const auto estimate = cuda::std::round(estimator.estimate(stream));
|
||||
|
||||
const double expected_count = static_cast<double>(num_items) / static_cast<double>(repeats);
|
||||
const double relative_error = std::abs((static_cast<double>(estimate) / expected_count) - 1.0);
|
||||
// RSD for a given precision is given by the following formula
|
||||
const double expected_standard_deviation = 1.04 / std::sqrt(static_cast<double>(1ull << expected_hll_precision));
|
||||
|
||||
// Check if the error is acceptable
|
||||
REQUIRE(relative_error < expected_standard_deviation * tolerance_factor);
|
||||
}
|
||||
|
||||
C2H_TEST("HyperLogLog precision constructor", "[hyperloglog]")
|
||||
{
|
||||
using T = int;
|
||||
using estimator_type = cudax::cuco::hyperloglog<T>;
|
||||
|
||||
const auto precision_value = GENERATE(4, 6, 8, 12, 16, 18);
|
||||
|
||||
const estimator_type::precision precision(precision_value);
|
||||
const auto expected_sketch_bytes = 4 * (1ull << precision_value);
|
||||
|
||||
CAPTURE(precision_value, expected_sketch_bytes);
|
||||
|
||||
REQUIRE(estimator_type::sketch_bytes(precision) == expected_sketch_bytes);
|
||||
|
||||
::cuda::stream stream{::cuda::device_ref{0}};
|
||||
auto mr = ::cuda::device_default_memory_pool(::cuda::device_ref{0});
|
||||
|
||||
estimator_type estimator{stream, mr, precision};
|
||||
|
||||
REQUIRE(estimator.sketch_bytes() == expected_sketch_bytes);
|
||||
REQUIRE(estimator.estimate(stream) == 0);
|
||||
}
|
||||
|
||||
C2H_TEST("HyperLogLog estimate preserves fractional cardinality", "[hyperloglog]")
|
||||
{
|
||||
using estimator_type = cudax::cuco::hyperloglog<int32_t>;
|
||||
|
||||
cuda::stream stream{cuda::device_ref{0}};
|
||||
auto mr = cuda::device_default_memory_pool(cuda::device_ref{0});
|
||||
|
||||
estimator_type estimator{stream, mr, estimator_type::precision{8}};
|
||||
const auto item = cuda::counting_iterator<int32_t>{0};
|
||||
estimator.add(stream, item, item + 1);
|
||||
|
||||
const auto estimate = estimator.estimate(stream);
|
||||
REQUIRE(estimate > 1.0);
|
||||
REQUIRE(estimate < 2.0);
|
||||
}
|
||||
|
||||
C2H_TEST("HyperLogLog ref validates sketch storage size", "[hyperloglog]")
|
||||
{
|
||||
using ref_type = cudax::cuco::hyperloglog_ref<int32_t>;
|
||||
|
||||
alignas(ref_type::sketch_alignment()) cuda::std::byte undersized_storage[32]{};
|
||||
REQUIRE_THROWS_WITH(ref_type{cuda::std::span<cuda::std::byte>{undersized_storage}},
|
||||
"Minimum required sketch size is 0.0625KB or 64B");
|
||||
|
||||
alignas(ref_type::sketch_alignment()) cuda::std::byte rounded_storage[96]{};
|
||||
const ref_type ref{cuda::std::span<cuda::std::byte>{rounded_storage}};
|
||||
REQUIRE(ref.sketch_bytes() == 64);
|
||||
}
|
||||
|
||||
#if _CCCL_CTK_AT_LEAST(12, 9) // Pinned memory resource is only supported with CTK 12.9 and later
|
||||
C2H_TEST("Hyperloglog estimate works with pinned memory pool", "[hyperloglog]")
|
||||
{
|
||||
using T = int32_t;
|
||||
using estimator_type = cudax::cuco::hyperloglog<T>;
|
||||
|
||||
const std::size_t num_items = 1 << 20;
|
||||
const int hll_precision = 12;
|
||||
const typename estimator_type::sketch_size_kb sketch_size_kb(4.0 * (1ull << hll_precision) / 1024.0);
|
||||
|
||||
CAPTURE(num_items, hll_precision, sketch_size_kb);
|
||||
|
||||
constexpr double tolerance_factor = 2.5;
|
||||
const double relative_standard_deviation = 1.04 / std::sqrt(static_cast<double>(1ull << hll_precision));
|
||||
|
||||
::cuda::stream stream{::cuda::device_ref{0}};
|
||||
auto mr = ::cuda::device_default_memory_pool(::cuda::device_ref{0});
|
||||
|
||||
auto items = ::cuda::make_buffer<T>(stream, mr, num_items, ::cuda::no_init);
|
||||
thrust::sequence(thrust::cuda::par_nosync.on(stream.get()), items.begin(), items.end(), T{0});
|
||||
|
||||
estimator_type estimator{stream, mr, sketch_size_kb};
|
||||
estimator.add(stream, items.begin(), items.end());
|
||||
|
||||
auto host_mr = ::cuda::pinned_default_memory_pool();
|
||||
const auto estimate = estimator.estimate(stream, host_mr);
|
||||
|
||||
const double relative_error = std::abs((static_cast<double>(estimate) / static_cast<double>(num_items)) - 1.0);
|
||||
|
||||
REQUIRE(relative_error < tolerance_factor * relative_standard_deviation);
|
||||
}
|
||||
#endif // _CCCL_CTK_AT_LEAST(12, 9)
|
||||
@@ -1,58 +0,0 @@
|
||||
//===----------------------------------------------------------------------===//
|
||||
//
|
||||
// Part of CUDA Experimental in CUDA C++ Core Libraries,
|
||||
// under the Apache License v2.0 with LLVM Exceptions.
|
||||
// See https://llvm.org/LICENSE.txt for license information.
|
||||
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
||||
// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES.
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
#include <cuda/std/cstddef>
|
||||
|
||||
#include <cuda/experimental/__cuco/capacity.cuh>
|
||||
#include <cuda/experimental/__cuco/hash_functions.cuh>
|
||||
#include <cuda/experimental/__cuco/probing_scheme.cuh>
|
||||
|
||||
#include <testing.cuh>
|
||||
|
||||
namespace cudax = cuda::experimental;
|
||||
|
||||
C2H_TEST("cuco make_valid_capacity rounding and validation", "[capacity]")
|
||||
{
|
||||
using probing = cudax::cuco::double_hashing<1, cudax::cuco::hash<int>>;
|
||||
[[maybe_unused]] constexpr int bucket = 1;
|
||||
|
||||
static_assert(cudax::cuco::is_double_hashing_v<probing>, "scheme is double hashing");
|
||||
|
||||
// make_valid_capacity rounds up and is idempotent; is_valid_capacity is derived from it
|
||||
constexpr auto valid = cudax::cuco::make_valid_capacity<probing, bucket>(::cuda::std::size_t{1000});
|
||||
static_assert(valid >= 1000, "rounds up");
|
||||
static_assert(cudax::cuco::is_valid_capacity<probing, bucket>(valid), "result is valid");
|
||||
static_assert(cudax::cuco::make_valid_capacity<probing, bucket>(valid) == valid, "idempotent");
|
||||
|
||||
// 1000 is not a valid double-hashing capacity; it rounds up to a prime-cycle capacity
|
||||
static_assert(!cudax::cuco::is_valid_capacity<probing, bucket>(::cuda::std::size_t{1000}), "1000 is not valid");
|
||||
|
||||
// equal-rounding requests produce the same valid capacity
|
||||
static_assert(cudax::cuco::make_valid_capacity<probing, bucket>(::cuda::std::size_t{1000})
|
||||
== cudax::cuco::make_valid_capacity<probing, bucket>(::cuda::std::size_t{1008}),
|
||||
"requests that round to the same capacity agree");
|
||||
|
||||
// cuCollections extent_test parity: double hashing, cg_size 2, bucket_size 4.
|
||||
// 1234 rounds up to next_prime(ceil(1234 / 8) = 155) = 157, times the stride 8 -> 1256.
|
||||
using dh4 = cudax::cuco::double_hashing<2, cudax::cuco::hash<int>>;
|
||||
[[maybe_unused]] constexpr int bucket4 = 4;
|
||||
static_assert(cudax::cuco::make_valid_capacity<dh4, bucket4>(::cuda::std::size_t{1234}) == ::cuda::std::size_t{1256},
|
||||
"compile-time valid capacity matches the cuCollections extent test");
|
||||
REQUIRE(cudax::cuco::make_valid_capacity<dh4, bucket4>(::cuda::std::size_t{1234}) == ::cuda::std::size_t{1256});
|
||||
|
||||
// a desired load factor outside (0, 1] is rejected
|
||||
using lp4 = cudax::cuco::linear_probing<2, cudax::cuco::hash<int>>;
|
||||
auto bad_lf = [](double __lf) {
|
||||
[[maybe_unused]] auto __r = cudax::cuco::make_valid_capacity<lp4, bucket4>(::cuda::std::size_t{1000}, __lf);
|
||||
};
|
||||
REQUIRE_THROWS(bad_lf(0.0));
|
||||
REQUIRE_THROWS(bad_lf(-0.5));
|
||||
REQUIRE_THROWS(bad_lf(1.5));
|
||||
}
|
||||
@@ -1,243 +0,0 @@
|
||||
//===----------------------------------------------------------------------===//
|
||||
//
|
||||
// Part of CUDA Experimental in CUDA C++ Core Libraries,
|
||||
// under the Apache License v2.0 with LLVM Exceptions.
|
||||
// See https://llvm.org/LICENSE.txt for license information.
|
||||
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
||||
// SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES.
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
#include <thrust/detail/raw_pointer_cast.h>
|
||||
|
||||
#include <cuda/std/array>
|
||||
#include <cuda/std/cstddef>
|
||||
#include <cuda/std/functional>
|
||||
#include <cuda/std/limits>
|
||||
#include <cuda/std/span>
|
||||
|
||||
#include <cuda/experimental/__cuco/hash_functions.cuh>
|
||||
|
||||
#include <testing.cuh>
|
||||
|
||||
#include <catch2/catch_template_test_macros.hpp>
|
||||
#include <catch2/catch_test_macros.hpp>
|
||||
|
||||
template <int32_t Words>
|
||||
struct large_key
|
||||
{
|
||||
constexpr _CCCL_HOST_DEVICE large_key(int32_t value) noexcept
|
||||
{
|
||||
for (int32_t i = 0; i < Words; ++i)
|
||||
{
|
||||
data_[i] = value;
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
int32_t data_[Words];
|
||||
};
|
||||
|
||||
template <cudax::cuco::hash_algorithm Algorithm>
|
||||
struct hash_test
|
||||
{
|
||||
template <typename Key, typename ResultT, typename... HashConstructorArgs>
|
||||
_CCCL_HOST_DEVICE void
|
||||
operator()(Key const& key, ResultT expected, HashConstructorArgs&&... hash_constructor_args) noexcept
|
||||
{
|
||||
cudax::cuco::hash<Key, Algorithm> hasher(::cuda::std::forward<HashConstructorArgs>(hash_constructor_args)...);
|
||||
|
||||
cuda::std::array<Key, 1> arr_keys = {key};
|
||||
|
||||
REQUIRE(hasher(key) == expected);
|
||||
REQUIRE(hasher(cuda::std::span<Key>(thrust::raw_pointer_cast(arr_keys.data()), arr_keys.size())) == expected);
|
||||
}
|
||||
};
|
||||
|
||||
struct test_xxhash32
|
||||
{
|
||||
hash_test<cudax::cuco::hash_algorithm::xxhash_32> xxhash32_test;
|
||||
|
||||
_CCCL_HOST_DEVICE void operator()()
|
||||
{
|
||||
xxhash32_test(static_cast<char>(0), 3479547966u, 0);
|
||||
xxhash32_test(static_cast<char>(42), 3774771295u, 0);
|
||||
xxhash32_test(static_cast<char>(0), 2099223482u, 42);
|
||||
xxhash32_test(static_cast<int32_t>(0), 148298089u, 0);
|
||||
xxhash32_test(static_cast<int32_t>(0), 2132181312u, 42);
|
||||
xxhash32_test(static_cast<int32_t>(42), 1161967057u, 0);
|
||||
xxhash32_test(static_cast<int32_t>(123456789), 2987034094u, 0);
|
||||
xxhash32_test(static_cast<int64_t>(0), 3736311059u, 0);
|
||||
xxhash32_test(static_cast<int64_t>(0), 1076387279u, 42);
|
||||
xxhash32_test(static_cast<int64_t>(42), 2332451213u, 0);
|
||||
xxhash32_test(static_cast<int64_t>(123456789), 1561711919u, 0);
|
||||
#if _CCCL_HAS_INT128()
|
||||
xxhash32_test(static_cast<__int128_t>(123456789), 1846633701u, 0);
|
||||
#endif
|
||||
xxhash32_test(large_key<32>(123456789), 3715432378u, 0);
|
||||
}
|
||||
};
|
||||
|
||||
struct test_xxhash64
|
||||
{
|
||||
hash_test<cudax::cuco::hash_algorithm::xxhash_64> xxhash64_test;
|
||||
|
||||
_CCCL_HOST_DEVICE void operator()()
|
||||
{
|
||||
xxhash64_test(static_cast<char>(0), 16804241149081757544ull, 0);
|
||||
xxhash64_test(static_cast<char>(42), 765293966243412708ull, 0);
|
||||
xxhash64_test(static_cast<char>(0), 9486749600008296231ull, 42);
|
||||
xxhash64_test(static_cast<int32_t>(0), 4246796580750024372ull, 0);
|
||||
xxhash64_test(static_cast<int32_t>(0), 3614696996920510707ull, 42);
|
||||
xxhash64_test(static_cast<int32_t>(42), 15516826743637085169ull, 0);
|
||||
xxhash64_test(static_cast<int32_t>(123456789), 9462334144942111946ull, 0);
|
||||
xxhash64_test(static_cast<int64_t>(0), 3803688792395291579ull, 0);
|
||||
xxhash64_test(static_cast<int64_t>(0), 13194218611613725804ull, 42);
|
||||
xxhash64_test(static_cast<int64_t>(42), 13066772586158965587ull, 0);
|
||||
xxhash64_test(static_cast<int64_t>(123456789), 14662639848940634189ull, 0);
|
||||
#if _CCCL_HAS_INT128()
|
||||
xxhash64_test(static_cast<__int128_t>(123456789), 7986913354431084250ull, 0);
|
||||
#endif
|
||||
xxhash64_test(large_key<32>(123456789), 2031761887105658523ull, 0);
|
||||
}
|
||||
};
|
||||
|
||||
struct test_murmurhash3_32
|
||||
{
|
||||
hash_test<cudax::cuco::hash_algorithm::murmurhash3_32> murmurhash3_32_test;
|
||||
|
||||
_CCCL_HOST_DEVICE void operator()()
|
||||
{
|
||||
murmurhash3_32_test(static_cast<char>(0), 1364076727u, 0);
|
||||
murmurhash3_32_test(static_cast<char>(42), 338914844u, 0);
|
||||
murmurhash3_32_test(static_cast<char>(0), 3712240066u, 42);
|
||||
murmurhash3_32_test(static_cast<int32_t>(0), 593689054u, 0);
|
||||
murmurhash3_32_test(static_cast<int32_t>(0), 933211791u, 42);
|
||||
murmurhash3_32_test(static_cast<int32_t>(42), 3160117731u, 0);
|
||||
murmurhash3_32_test(static_cast<int32_t>(123456789), 3206620847u, 0);
|
||||
murmurhash3_32_test(static_cast<int64_t>(0), 1669671676u, 0);
|
||||
murmurhash3_32_test(static_cast<int64_t>(0), 2624043101u, 42);
|
||||
murmurhash3_32_test(static_cast<int64_t>(42), 1871679806u, 0);
|
||||
murmurhash3_32_test(static_cast<int64_t>(123456789), 690028081u, 0);
|
||||
#if _CCCL_HAS_INT128()
|
||||
murmurhash3_32_test(static_cast<__int128_t>(123456789), 2191144977u, 0);
|
||||
#endif
|
||||
murmurhash3_32_test(large_key<32>(123456789), 2555553099u, 0);
|
||||
}
|
||||
};
|
||||
|
||||
#if _CCCL_HAS_INT128()
|
||||
struct test_murmurhash3_x86_128
|
||||
{
|
||||
hash_test<cudax::cuco::hash_algorithm::murmurhash3_x86_128> murmurhash3_x86_128_test;
|
||||
|
||||
_CCCL_HOST_DEVICE __uint128_t conv(cuda::std::array<uint32_t, 4> const& arr) const
|
||||
{
|
||||
return cuda::std::bit_cast<__uint128_t>(arr);
|
||||
}
|
||||
|
||||
_CCCL_HOST_DEVICE void operator()()
|
||||
{
|
||||
murmurhash3_x86_128_test(int32_t(0), conv({3422973727u, 2656139328u, 2656139328u, 2656139328u}), 0);
|
||||
murmurhash3_x86_128_test(int32_t(9), conv({2808089785u, 314604614u, 314604614u, 314604614u}), 0);
|
||||
murmurhash3_x86_128_test(int32_t(42), conv({3611919118u, 1962256489u, 1962256489u, 1962256489u}), 0);
|
||||
murmurhash3_x86_128_test(int32_t(42), conv({3399017053u, 732469929u, 732469929u, 732469929u}), 42);
|
||||
|
||||
murmurhash3_x86_128_test(
|
||||
cuda::std::array<int32_t, 2>{2, 2}, conv({1234494082u, 1431451587u, 431049201u, 431049201u}), 0);
|
||||
murmurhash3_x86_128_test(
|
||||
cuda::std::array<int32_t, 3>{1, 4, 9}, conv({2516796247u, 2757675829u, 778406919u, 2453259553u}), 42);
|
||||
murmurhash3_x86_128_test(
|
||||
cuda::std::array<int32_t, 4>{42, 64, 108, 1024}, conv({2686265656u, 591236665u, 3797082165u, 2731908938u}), 63);
|
||||
murmurhash3_x86_128_test(cuda::std::array<int32_t, 16>{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16},
|
||||
conv({3918256832u, 4205523739u, 1707810111u, 1625952473u}),
|
||||
1024);
|
||||
|
||||
murmurhash3_x86_128_test(
|
||||
cuda::std::array<int64_t, 2>{2, 2}, conv({3811075945u, 727160712u, 3510740342u, 235225510u}), 0);
|
||||
murmurhash3_x86_128_test(
|
||||
cuda::std::array<int64_t, 3>{1, 4, 9}, conv({2817194959u, 206796677u, 3391242768u, 248681098u}), 42);
|
||||
murmurhash3_x86_128_test(
|
||||
cuda::std::array<int64_t, 4>{42, 64, 108, 1024}, conv({2335912146u, 1566515912u, 760710030u, 452077451u}), 63);
|
||||
murmurhash3_x86_128_test(cuda::std::array<int64_t, 16>{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16},
|
||||
conv({1101169764u, 1758958147u, 2406511780u, 2903571412u}),
|
||||
1024);
|
||||
}
|
||||
};
|
||||
|
||||
struct test_murmurhash3_x64_128
|
||||
{
|
||||
hash_test<cudax::cuco::hash_algorithm::murmurhash3_x64_128> murmurhash3_x64_128_test;
|
||||
|
||||
_CCCL_HOST_DEVICE __uint128_t conv(cuda::std::array<uint64_t, 2> const& arr) const
|
||||
{
|
||||
return cuda::std::bit_cast<__uint128_t>(arr);
|
||||
}
|
||||
|
||||
_CCCL_HOST_DEVICE void operator()()
|
||||
{
|
||||
murmurhash3_x64_128_test(int32_t(0), conv({14961230494313510588ull, 6383328099726337777ull}), 0);
|
||||
murmurhash3_x64_128_test(int32_t(9), conv({1779292183511753683ull, 16298496441448380334ull}), 0);
|
||||
murmurhash3_x64_128_test(int32_t(42), conv({2913627637088662735ull, 16344193523890567190ull}), 0);
|
||||
murmurhash3_x64_128_test(int32_t(42), conv({2248879576374326886ull, 18006515275339376488ull}), 42);
|
||||
|
||||
murmurhash3_x64_128_test(
|
||||
cuda::std::array<int32_t, 2>{2, 2}, conv({12221386834995143465ull, 6690950894782946573ull}), 0);
|
||||
murmurhash3_x64_128_test(
|
||||
cuda::std::array<int32_t, 3>{1, 4, 9}, conv({299140022350411792ull, 9891903873182035274ull}), 42);
|
||||
murmurhash3_x64_128_test(
|
||||
cuda::std::array<int32_t, 4>{42, 64, 108, 1024}, conv({4333511168876981289ull, 4659486988434316416ull}), 63);
|
||||
murmurhash3_x64_128_test(cuda::std::array<int32_t, 16>{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16},
|
||||
conv({3302412811061286680ull, 7070355726356610672ull}),
|
||||
1024);
|
||||
|
||||
murmurhash3_x64_128_test(
|
||||
cuda::std::array<int64_t, 2>{2, 2}, conv({8554944597931919519ull, 14938998000509429729ull}), 0);
|
||||
murmurhash3_x64_128_test(
|
||||
cuda::std::array<int64_t, 3>{1, 4, 9}, conv({13442629947720186435ull, 7061727494178573325ull}), 42);
|
||||
murmurhash3_x64_128_test(
|
||||
cuda::std::array<int64_t, 4>{42, 64, 108, 1024}, conv({8786399719555989948ull, 14954183901757012458ull}), 63);
|
||||
murmurhash3_x64_128_test(cuda::std::array<int64_t, 16>{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16},
|
||||
conv({15409921801541329777ull, 10546487400963404004ull}),
|
||||
1024);
|
||||
}
|
||||
};
|
||||
#endif // _CCCL_HAS_INT128()
|
||||
|
||||
template <typename TestFn>
|
||||
__global__ void test_hasher_kernel(TestFn test_fn)
|
||||
{
|
||||
test_fn();
|
||||
}
|
||||
|
||||
template <typename TestFn>
|
||||
void test_hasher_on_device(TestFn test_fn)
|
||||
{
|
||||
test_hasher_kernel<<<1, 1>>>(test_fn);
|
||||
REQUIRE_CUDART(cudaDeviceSynchronize());
|
||||
}
|
||||
|
||||
TEST_CASE("Test Hasher's on host and device", "")
|
||||
{
|
||||
SECTION("host-generated hash values match the reference implementation.")
|
||||
{
|
||||
test_xxhash32{}();
|
||||
test_xxhash64{}();
|
||||
test_murmurhash3_32{}();
|
||||
#if _CCCL_HAS_INT128()
|
||||
test_murmurhash3_x86_128{}();
|
||||
test_murmurhash3_x64_128{}();
|
||||
#endif // _CCCL_HAS_INT128()
|
||||
}
|
||||
|
||||
SECTION("device-generated hash values match the reference implementation.")
|
||||
{
|
||||
test_hasher_on_device(test_xxhash32{});
|
||||
test_hasher_on_device(test_xxhash64{});
|
||||
test_hasher_on_device(test_murmurhash3_32{});
|
||||
#if _CCCL_HAS_INT128()
|
||||
test_hasher_on_device(test_murmurhash3_x86_128{});
|
||||
test_hasher_on_device(test_murmurhash3_x64_128{});
|
||||
#endif // _CCCL_HAS_INT128()
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user