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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
#include <cuda/experimental/__stf/internal/backend_ctx.cuh>
#include <cuda/experimental/__stf/utility/getenv_cache.cuh>
#include <cuda/experimental/__stf/utility/memory.cuh>
#include <cuda/experimental/__stf/utility/unstable_unique.cuh>
#include <cuda/experimental/__utility/unstable_unique.cuh>

#include <mutex>

Expand Down Expand Up @@ -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;
Expand Down
173 changes: 0 additions & 173 deletions cudax/include/cuda/experimental/__stf/utility/unstable_unique.cuh

This file was deleted.

105 changes: 105 additions & 0 deletions cudax/include/cuda/experimental/__utility/unstable_unique.cuh
Original file line number Diff line number Diff line change
@@ -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
Comment on lines +11 to +12
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since this is an algorithm, I think it should go to <cuda/experimental/algorithm>


#include <cuda/std/detail/__config>

#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 <cuda/std/__cccl/assert.h>
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
#include <cuda/std/__cccl/assert.h>

all <cuda/std/__cccl/meow.h> headers are already included through the <cuda/std/detail/__config> header

#include <cuda/std/__iterator/next.h>
#include <cuda/std/__iterator/prev.h>
#include <cuda/std/__utility/move.h>

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 <class _Iterator, class _BinaryPredicate>
_CCCL_HOST_API _Iterator unstable_unique(_Iterator __first, _Iterator __last, _BinaryPredicate __pred)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems like this algorithm requires random access iterators. Consider static_assert()-ing (and documenting) that.

Note ++ and -- makes for bidirectional iterators, and I believe < means it needs random-access. If you can make the outer if condition if (; __first != __last; ++__first) and hide the asserts behind

if constexpr (/* is_random_access_iterator */)

then you could at least relax to bidirectional.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually it should work with bidir. Though improving the algo wasn't the primary purpose of this PR, we appreciate the review and take the opportunity to polish the code a lil.
I'll add specific suggestions to make the algo work with bidirectional iterators.

{
if (__first == __last || ::cuda::std::next(__first) == __last)
{
return __last;
}

++__first;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can be moved into the first clause of the for-loop

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice, though we lose the comment (which was lost already lol).

bool __first_is_known_duplicate = false;

for (; __first < __last; ++__first)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
for (; __first < __last; ++__first)
for (; __first != __last; ++__first)

to make it work with bidir

{
if (!__first_is_known_duplicate)
{
if (!__pred(*__first, *::cuda::std::prev(__first)))
{
continue;
}
}
_CCCL_ASSERT(__first < __last, "unstable_unique: iterator out of range");
Copy link
Copy Markdown
Contributor

@andralex andralex Mar 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
_CCCL_ASSERT(__first < __last, "unstable_unique: iterator out of range");
_CCCL_ASSERT(__first != __last, "unstable_unique: iterator out of range");

also for bidir

for (--__last;; --__last)
{
if (__first == __last)
{
return __first;
}
_CCCL_ASSERT(__first < __last, "unstable_unique: iterator out of range");
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
_CCCL_ASSERT(__first < __last, "unstable_unique: iterator out of range");
_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 <class _Iterator>
_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;
});
Comment on lines +99 to +101
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Prefer ::cuda::std::equal_to<>{} here I think.

Comment on lines +99 to +101
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
return ::cuda::experimental::unstable_unique(__first, __last, [](const auto& __a, const auto& __b) {
return __a == __b;
});
return ::cuda::experimental::unstable_unique(__first, __last, ::cuda::std::equal_to<>{});

nice, thx @Jacobfaib

}
} // namespace cuda::experimental

#endif // _CUDAX__UTILITY_UNSTABLE_UNIQUE
1 change: 1 addition & 0 deletions cudax/test/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
)

Expand Down
1 change: 0 additions & 1 deletion cudax/test/stf/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
74 changes: 74 additions & 0 deletions cudax/test/utility/unstable_unique.cu
Original file line number Diff line number Diff line change
@@ -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 <cuda/experimental/__utility/unstable_unique.cuh>

#include <vector>

#include <catch2/catch_test_macros.hpp>

namespace cudax = cuda::experimental;

TEST_CASE("unstable_unique empty range", "[utility]")
{
std::vector<int> v;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider adding some tests with other iterator categories. For example std::list or std::set for bidirectional iterators.

auto new_end = cudax::unstable_unique(v.begin(), v.end());
REQUIRE(v.end() == new_end);
}

TEST_CASE("unstable_unique no duplicates", "[utility]")
{
std::vector<int> v = {1, 2, 3, 4, 5};
auto new_end = cudax::unstable_unique(v.begin(), v.end());
REQUIRE(v.end() == new_end);
REQUIRE(std::vector<int>({1, 2, 3, 4, 5}) == v);
}

TEST_CASE("unstable_unique leading duplicates", "[utility]")
{
std::vector<int> 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<int>({1, 5, 2, 3, 4, 5}) == v);
}

TEST_CASE("unstable_unique interleaved duplicates", "[utility]")
{
std::vector<int> 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<int>({1, 5, 2, 4, 3, 3, 4, 4, 5, 5}) == v);
}

TEST_CASE("unstable_unique all same", "[utility]")
{
std::vector<int> 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<int>({1, 1, 1, 1, 1}) == v);
}

TEST_CASE("unstable_unique trailing unique", "[utility]")
{
std::vector<int> 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<int>({1, 2, 1, 1, 1, 2}) == v);
}

TEST_CASE("unstable_unique with custom predicate", "[utility]")
{
std::vector<int> 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<int>{1, 5, 4, 3, 2, 1, 1, 1, 1, 2, 2, 2, 3, 4, 5} == v);
}