Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Factory method for cuco::static_set #297

Draft
wants to merge 7 commits into
base: dev
Choose a base branch
from

Conversation

sleeepyjack
Copy link
Collaborator

@sleeepyjack sleeepyjack commented Apr 26, 2023

This PR adds a factory make_static_set<Key, Scope>(args…), which takes a parameter pack of ctor arguments in any order and returns a static_set object.

Closes #207

@sleeepyjack sleeepyjack added the topic: static_set Issue related to the static_set label Apr 26, 2023
@sleeepyjack
Copy link
Collaborator Author

Marking this as a draft since there are several details I'd like to discuss in the reviews. Also, some docs are still missing.

@sleeepyjack sleeepyjack added the type: feature request New feature request label Apr 26, 2023
Comment on lines -50 to +53
cuco::experimental::static_set<Key> set{
size, cuco::empty_key<Key>{-1}, {}, {}, {}, {launch.get_stream()}};
auto set = cuco::experimental::make_static_set<Key>(
cuco::experimental::extent<std::size_t>{size},
cuco::empty_key<Key>{-1},
cuco::experimental::cuda_stream_ref{launch.get_stream()});
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Using the factory doesn't save you any characters (at least until we remove the experimental namespace). However, not having to remember the exact order of arguments is a huge win in my UI book.

Copy link
Member

Choose a reason for hiding this comment

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

I assume size and empty sentinel are necessary and all others are optional?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Yep. Also, for the tparams, only the Key is necessary.

Extent capacity,
empty_key<Key> empty_key_sentinel,
KeyEqual pred,
ProbingScheme const& probing_scheme,
Allocator const& alloc,
[[maybe_unused]] Storage const& storage,
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I don't like this. We're using storage as a dummy object here to deduce the Storage type from. A user might misinterpret that as an opportunity to pass in an existing storage for the container to use.

Copy link
Collaborator Author

@sleeepyjack sleeepyjack Apr 26, 2023

Choose a reason for hiding this comment

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

I have a solution in mind that eliminates the storage argument from the ctor. I'm still fiddling with it a bit.

Comment on lines -85 to +86
class Extent = cuco::experimental::extent<std::size_t>,
cuda::thread_scope Scope = cuda::thread_scope_device,
class Extent = cuco::experimental::extent<std::size_t>,
Copy link
Collaborator Author

@sleeepyjack sleeepyjack Apr 26, 2023

Choose a reason for hiding this comment

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

Since cuda::thread_scope is an enum type, for it to be available at compile time, we need to pass it in the template parameter list of the factory instead of the runtime argument list:

make_static_set<Key>(cuda::thread_scope_device, ...); // invalid; value is only known at runtime
make_static_set<Key, cuda::thread_scope_device>(...); // valid; value is known at compile time

Since all other tparams can be deduced from the parameter pack of the factory, I had to move the non-deducible tparams to the front.

Comment on lines +36 to +37
struct extent : private detail::extent_base<SizeType> {
using value_type = typename detail::extent_base<SizeType>::value_type; ///< Extent value type
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Adding this base class was also necessary so I could provide a trait which checks if a given type is cuco::extent-like

Comment on lines +98 to +116
template <typename Key>
struct key_equal_traits {
template <typename T, typename = std::void_t<>>
struct is_equal_functor : std::false_type {
};

template <typename T>
struct is_equal_functor<
T,
std::void_t<std::enable_if_t<std::is_invocable_r_v<bool, T, Key const&, Key const&>>>>
: std::true_type {
};

template <typename T>
using is_equal_functor_t = typename is_equal_functor<T>::type;

template <typename T>
static constexpr bool is_equal_functor_v = is_equal_functor<T>::value;
};
Copy link
Collaborator Author

@sleeepyjack sleeepyjack Apr 26, 2023

Choose a reason for hiding this comment

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

This was a bit tricky. I needed a trait, which checks if a given user type T is a binary functor (Key, Key) -> bool. However, the find_arg function only knows of T, and does not directly know of Key. That's why this is a nested trait which can be passed as find_arg<key_equal_traits<Key>::template is_equal_functor_t>(...).
It works, but I'm not proud of it. So I'm very open to suggestions to make this more elegant to use.

template <class Key, cuda::thread_scope Scope, class... Args>
constexpr auto make_static_set(Args&&... args)
{
// TODO don't repeat defaults
Copy link
Collaborator Author

@sleeepyjack sleeepyjack Apr 26, 2023

Choose a reason for hiding this comment

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

Yeah, that's also not so elegant. Currently, we need to mirror every default tparam from static_set explicitly. If we change the default in static_set, but forget to change it in the factory, that would be bad.
Maybe I can instantiate a set with default tparams first, and then extract the tparams from its member type aliases:

using default_set = static_set<Key>;
using default_allocator = typename default_set::allocator_type;
...

@jrhemstad
Copy link
Collaborator

Why is it a factory instead of just a constructor? Factory functions are largely defunct as of C++17 and the introduction of CTAD.

@sleeepyjack
Copy link
Collaborator Author

Why is it a factory instead of just a constructor? Factory functions are largely defunct as of C++17 and the introduction of CTAD.

I fiddled around with this and the problem with the initial design was that it required a deduction guide in order to correctly deduce the tparams from the find_arg constructs. The problem was that I also had to pass in some tparams explicitly, which couldn't be deduced from the args pack. Since partial deduction guides aren't a thing, I ran into a dead end.
The factory function, on the other hand, gives me an extra layer of indirection so I don't need any deduction guides.

That said, I'm working on a fix that enables us to deduce all of the tparams from the args pack, thus shipping around the problem with the deduction guide. Once this is working flawlessly with the factory function, I can give it another try integrating it into the ctor directly.

Another point to consider is if we directly integrate this into the ctor, we always pay for the extra compile time introduced by the find_arg function.
I don't know if downstream projects like RAPIDS will pay this fee. Providing the old-school ctor gives them a fallback in case compilation times are of concern.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
topic: static_set Issue related to the static_set type: feature request New feature request
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[FEA] Variadic ctor parameters
3 participants