diff --git a/cudax/include/cuda/experimental/__stf/stream/internal/event_types.cuh b/cudax/include/cuda/experimental/__stf/stream/internal/event_types.cuh index 4322a2e0559..0558be64047 100644 --- a/cudax/include/cuda/experimental/__stf/stream/internal/event_types.cuh +++ b/cudax/include/cuda/experimental/__stf/stream/internal/event_types.cuh @@ -25,7 +25,7 @@ #include #include #include -#include +#include #include @@ -196,11 +196,12 @@ public: // Remove duplicates. Two events are duplicates if they have the same stream. // Will keep the first element of each duplicate run, which is the one with the largest id. - proxy.erase(unstable_unique(proxy.begin(), - proxy.end(), - [](const auto& a, const auto& b) { - return a->dstream.stream == b->dstream.stream; - }), + proxy.erase(::cuda::experimental::unstable_unique( + proxy.begin(), + proxy.end(), + [](const auto& a, const auto& b) { + return a->dstream.stream == b->dstream.stream; + }), proxy.end()); return true; diff --git a/cudax/include/cuda/experimental/__stf/utility/unstable_unique.cuh b/cudax/include/cuda/experimental/__stf/utility/unstable_unique.cuh deleted file mode 100644 index 1dbc8e8e57e..00000000000 --- a/cudax/include/cuda/experimental/__stf/utility/unstable_unique.cuh +++ /dev/null @@ -1,173 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// Part of CUDASTF 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) 2022-2024 NVIDIA CORPORATION & AFFILIATES. -// -//===----------------------------------------------------------------------===// - -#pragma once - -#include - -#if defined(_CCCL_IMPLICIT_SYSTEM_HEADER_GCC) -# pragma GCC system_header -#elif defined(_CCCL_IMPLICIT_SYSTEM_HEADER_CLANG) -# pragma clang system_header -#elif defined(_CCCL_IMPLICIT_SYSTEM_HEADER_MSVC) -# pragma system_header -#endif // no system header - -/** - * @file - * @brief Implementation of unstable_unique - */ - -#include - -#include -#include -#include - -namespace cuda::experimental::stf -{ -/** - * @brief Removes duplicates from a range using a custom predicate. - * - * This function operates from both sides of the range, moving elements from the right-hand side to the left to - * eliminate duplicates. The order of the elements is not preserved. - * - * @tparam iterator The type of the iterator. - * @tparam BinaryPredicate The type of the predicate. - * @param first, last The range of elements to remove duplicates from. - * @param p The predicate to use for comparing elements. - * - * @return iterator The new end of the range after duplicates have been removed. - * - * @par Example: - * @code - * ::std::vector v = {1, 1, 2, 2, 3, 3, 4, 4, 5, 5}; - * auto new_end = unstable_unique(v.begin(), v.end(), [](int a, int b) { return a == b; }); - * v.erase(new_end, v.end()); - * // v is now {1, 5, 2, 4, 3, 5} - * @endcode - */ -template -iterator unstable_unique(iterator first, iterator last, BinaryPredicate p) -{ - if (first == last || ::std::next(first) == last) - { - return last; // 0 or 1-element range - } - - ++first; // position first on the first unknown element (first element will stay by definition) - bool first_is_known_duplicate = false; // see below for description - - for (; first < last; ++first) - { - if (!first_is_known_duplicate) - { - if (!p(*first, *::std::prev(first))) - { - continue; - } - } - // Here we know that `*first` is a dupe and should be replaced. Also the range is not empty. - assert(first < last); - for (--last;; --last) - { - if (first == last) - { - return first; // just past the last unique element - } - assert(first < last); - if (!p(*last, *::std::prev(last))) - { - break; - } - } - assert(!p(*first, *last)); - // Here we know we're good to replace *first with *last. - // Complicating matter: if we do so, we "forget" whether *::std::next(first) is a duplicate of *first. - // Maintain `first_is_known_duplicate` to keep track of that. - first_is_known_duplicate = p(*first, *::std::next(first)); - *first = mv(*last); - } - - return first; -} - -/** - * @brief Removes duplicates from a range using the built-in operator==. - * - * This function operates like `unstable_unique` above with `operator==` as the predicate. - * - * @tparam iterator The type of the iterator. - * @param first, last The range of elements to remove duplicates from. - * - * @return iterator The new end of the range after duplicates have been removed. - * - * @par Example: - * @code - * ::std::vector v = {1, 1, 2, 2, 3, 3, 4, 4, 5, 5}; - * auto new_end = unstable_unique(v.begin(), v.end()); - * v.erase(new_end, v.end()); - * // v is now {1, 5, 2, 4, 3, 5} - * @endcode - */ -template -iterator unstable_unique(iterator first, iterator last) -{ - return unstable_unique(first, last, [](auto& a, auto& b) { - return a == b; - }); -} - -#ifdef UNITTESTED_FILE -UNITTEST("unstable_unique") -{ - ::std::vector v1; - auto new_end1 = unstable_unique(v1.begin(), v1.end()); - EXPECT(v1.end() == new_end1); - - ::std::vector v2 = {1, 2, 3, 4, 5}; - auto new_end2 = unstable_unique(v2.begin(), v2.end()); - // ::std::cout << new_end2 - v2.begin() << '\n'; - EXPECT(v2.end() == new_end2); - // ::std::copy(v2.begin(), new_end2, ::std::ostream_iterator(::std::cout, " ")); - EXPECT(::std::vector({1, 2, 3, 4, 5}) == v2); - - ::std::vector v3 = {1, 1, 2, 3, 4, 5}; - auto new_end3 = unstable_unique(v3.begin(), v3.end()); - // ::std::cerr << new_end3 - v3.begin() << '\n'; - EXPECT(v3.begin() + 5 == new_end3); - EXPECT(::std::vector({1, 5, 2, 3, 4, 5}) == v3); - ::std::vector v4 = {1, 1, 2, 2, 3, 3, 4, 4, 5, 5}; - auto new_end4 = unstable_unique(v4.begin(), v4.end()); - // ::std::cerr << new_end4 - v4.begin() << '\n'; - EXPECT(v4.begin() + 5 == new_end4); - EXPECT(::std::vector({1, 5, 2, 4, 3, 3, 4, 4, 5, 5}) == v4); - - ::std::vector v5 = {1, 1, 1, 1, 1}; - auto new_end5 = unstable_unique(v5.begin(), v5.end()); - EXPECT(1 + v5.begin() == new_end5); - EXPECT(::std::vector({1, 1, 1, 1, 1}) == v5); - - ::std::vector v6 = {1, 1, 1, 1, 1, 2}; - auto new_end6 = unstable_unique(v6.begin(), v6.end()); - EXPECT(v6.begin() + 2 == new_end6); - EXPECT(::std::vector({1, 2, 1, 1, 1, 2}) == v6); - - ::std::vector v7 = {1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 3, 4, 5}; - assert(v7.size() == 15); - auto new_end7 = unstable_unique(v7.begin(), v7.end(), [](int a, int b) { - return a == b; - }); - // ::std::cerr << new_end7 - v7.begin() << '\n'; - EXPECT(v7.begin() + 5 == new_end7); - EXPECT(::std::vector{1, 5, 4, 3, 2, 1, 1, 1, 1, 2, 2, 2, 3, 4, 5} == v7); -}; -#endif // UNITTESTED_FILE -} // end namespace cuda::experimental::stf diff --git a/cudax/include/cuda/experimental/__utility/unstable_unique.cuh b/cudax/include/cuda/experimental/__utility/unstable_unique.cuh new file mode 100644 index 00000000000..e52c7e9ec4b --- /dev/null +++ b/cudax/include/cuda/experimental/__utility/unstable_unique.cuh @@ -0,0 +1,105 @@ +//===----------------------------------------------------------------------===// +// +// 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) 2022-2025 NVIDIA CORPORATION & AFFILIATES. +// +//===----------------------------------------------------------------------===// + +#ifndef _CUDAX__UTILITY_UNSTABLE_UNIQUE +#define _CUDAX__UTILITY_UNSTABLE_UNIQUE + +#include + +#if defined(_CCCL_IMPLICIT_SYSTEM_HEADER_GCC) +# pragma GCC system_header +#elif defined(_CCCL_IMPLICIT_SYSTEM_HEADER_CLANG) +# pragma clang system_header +#elif defined(_CCCL_IMPLICIT_SYSTEM_HEADER_MSVC) +# pragma system_header +#endif // no system header + +#include +#include +#include +#include + +namespace cuda::experimental +{ +//! @brief Removes duplicates from a sorted range using a custom predicate. +//! +//! Operates from both sides of the range, moving elements from the right-hand +//! side to the left to eliminate duplicates. The relative order of elements is +//! not preserved. +//! +//! @tparam _Iterator The type of the iterator. +//! @tparam _BinaryPredicate The type of the predicate. +//! +//! @param[in] __first Iterator to the beginning of the range. +//! @param[in] __last Iterator past the end of the range. +//! @param[in] __pred The predicate used to compare adjacent elements. +//! +//! @return Iterator to the new end of the range after duplicates have been removed. +template +_CCCL_HOST_API _Iterator unstable_unique(_Iterator __first, _Iterator __last, _BinaryPredicate __pred) +{ + if (__first == __last || ::cuda::std::next(__first) == __last) + { + return __last; + } + + ++__first; + bool __first_is_known_duplicate = false; + + for (; __first < __last; ++__first) + { + if (!__first_is_known_duplicate) + { + if (!__pred(*__first, *::cuda::std::prev(__first))) + { + continue; + } + } + _CCCL_ASSERT(__first < __last, "unstable_unique: iterator out of range"); + for (--__last;; --__last) + { + if (__first == __last) + { + return __first; + } + _CCCL_ASSERT(__first < __last, "unstable_unique: iterator out of range"); + if (!__pred(*__last, *::cuda::std::prev(__last))) + { + break; + } + } + _CCCL_ASSERT(!__pred(*__first, *__last), "unstable_unique: unexpected duplicate"); + __first_is_known_duplicate = __pred(*__first, *::cuda::std::next(__first)); + *__first = ::cuda::std::move(*__last); + } + + return __first; +} + +//! @brief Removes duplicates from a sorted range using `operator==`. +//! +//! Equivalent to calling the predicate overload with `operator==`. +//! +//! @tparam _Iterator The type of the iterator. +//! +//! @param[in] __first Iterator to the beginning of the range. +//! @param[in] __last Iterator past the end of the range. +//! +//! @return Iterator to the new end of the range after duplicates have been removed. +template +_CCCL_HOST_API _Iterator unstable_unique(_Iterator __first, _Iterator __last) +{ + return ::cuda::experimental::unstable_unique(__first, __last, [](const auto& __a, const auto& __b) { + return __a == __b; + }); +} +} // namespace cuda::experimental + +#endif // _CUDAX__UTILITY_UNSTABLE_UNIQUE diff --git a/cudax/test/CMakeLists.txt b/cudax/test/CMakeLists.txt index 7ad05b9c879..0614e6d17b1 100644 --- a/cudax/test/CMakeLists.txt +++ b/cudax/test/CMakeLists.txt @@ -75,6 +75,7 @@ cudax_add_catch2_test(test_target stream cudax_add_catch2_test(test_target misc utility/ensure_current_device.cu + utility/unstable_unique.cu utility/optionally_static.cu ) diff --git a/cudax/test/stf/CMakeLists.txt b/cudax/test/stf/CMakeLists.txt index c53a4595fc5..d9ce645d88a 100644 --- a/cudax/test/stf/CMakeLists.txt +++ b/cudax/test/stf/CMakeLists.txt @@ -234,7 +234,6 @@ set( cuda/experimental/__stf/utility/scope_guard.cuh cuda/experimental/__stf/stackable/stackable_ctx.cuh cuda/experimental/__stf/utility/unittest.cuh - cuda/experimental/__stf/utility/unstable_unique.cuh ) cccl_get_cudatoolkit() diff --git a/cudax/test/utility/unstable_unique.cu b/cudax/test/utility/unstable_unique.cu new file mode 100644 index 00000000000..3f4548c66e1 --- /dev/null +++ b/cudax/test/utility/unstable_unique.cu @@ -0,0 +1,74 @@ +//===----------------------------------------------------------------------===// +// +// 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) 2022-2025 NVIDIA CORPORATION & AFFILIATES. +// +//===----------------------------------------------------------------------===// + +#include + +#include + +#include + +namespace cudax = cuda::experimental; + +TEST_CASE("unstable_unique empty range", "[utility]") +{ + std::vector v; + auto new_end = cudax::unstable_unique(v.begin(), v.end()); + REQUIRE(v.end() == new_end); +} + +TEST_CASE("unstable_unique no duplicates", "[utility]") +{ + std::vector v = {1, 2, 3, 4, 5}; + auto new_end = cudax::unstable_unique(v.begin(), v.end()); + REQUIRE(v.end() == new_end); + REQUIRE(std::vector({1, 2, 3, 4, 5}) == v); +} + +TEST_CASE("unstable_unique leading duplicates", "[utility]") +{ + std::vector v = {1, 1, 2, 3, 4, 5}; + auto new_end = cudax::unstable_unique(v.begin(), v.end()); + REQUIRE(v.begin() + 5 == new_end); + REQUIRE(std::vector({1, 5, 2, 3, 4, 5}) == v); +} + +TEST_CASE("unstable_unique interleaved duplicates", "[utility]") +{ + std::vector v = {1, 1, 2, 2, 3, 3, 4, 4, 5, 5}; + auto new_end = cudax::unstable_unique(v.begin(), v.end()); + REQUIRE(v.begin() + 5 == new_end); + REQUIRE(std::vector({1, 5, 2, 4, 3, 3, 4, 4, 5, 5}) == v); +} + +TEST_CASE("unstable_unique all same", "[utility]") +{ + std::vector v = {1, 1, 1, 1, 1}; + auto new_end = cudax::unstable_unique(v.begin(), v.end()); + REQUIRE(1 + v.begin() == new_end); + REQUIRE(std::vector({1, 1, 1, 1, 1}) == v); +} + +TEST_CASE("unstable_unique trailing unique", "[utility]") +{ + std::vector v = {1, 1, 1, 1, 1, 2}; + auto new_end = cudax::unstable_unique(v.begin(), v.end()); + REQUIRE(v.begin() + 2 == new_end); + REQUIRE(std::vector({1, 2, 1, 1, 1, 2}) == v); +} + +TEST_CASE("unstable_unique with custom predicate", "[utility]") +{ + std::vector v = {1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 3, 4, 5}; + auto new_end = cudax::unstable_unique(v.begin(), v.end(), [](int a, int b) { + return a == b; + }); + REQUIRE(v.begin() + 5 == new_end); + REQUIRE(std::vector{1, 5, 4, 3, 2, 1, 1, 1, 1, 2, 2, 2, 3, 4, 5} == v); +}