|
| 1 | +/* |
| 2 | + * Copyright (c) 2022, NVIDIA CORPORATION. |
| 3 | + * |
| 4 | + * Licensed under the Apache License, Version 2.0 (the "License"); |
| 5 | + * you may not use this file except in compliance with the License. |
| 6 | + * You may obtain a copy of the License at |
| 7 | + * |
| 8 | + * http://www.apache.org/licenses/LICENSE-2.0 |
| 9 | + * |
| 10 | + * Unless required by applicable law or agreed to in writing, software |
| 11 | + * distributed under the License is distributed on an "AS IS" BASIS, |
| 12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 13 | + * See the License for the specific language governing permissions and |
| 14 | + * limitations under the License. |
| 15 | + */ |
| 16 | + |
| 17 | +#pragma once |
| 18 | + |
| 19 | +#include <atomic> |
| 20 | +#include <cuco/detail/reduction_functor_impl.cuh> |
| 21 | + |
| 22 | +#include <cuda/atomic> |
| 23 | +#include <limits> |
| 24 | +#include <type_traits> |
| 25 | + |
| 26 | +namespace cuco { |
| 27 | + |
| 28 | +/** |
| 29 | + * @brief Wrapper for reduction identity value. |
| 30 | + * |
| 31 | + * @tparam T The underlying value type used for reduction |
| 32 | + */ |
| 33 | +template <typename T> |
| 34 | +class identity_value { |
| 35 | + public: |
| 36 | + using type = T; |
| 37 | + constexpr identity_value(T const& identity) noexcept : identity_(identity) {} |
| 38 | + constexpr T value() const noexcept { return identity_; } |
| 39 | + private: |
| 40 | + T identity_; |
| 41 | +}; |
| 42 | + |
| 43 | +/** |
| 44 | + * @brief Wrapper for a user-defined custom reduction operator. |
| 45 | + * |
| 46 | + * External synchronization, if required, |
| 47 | + * is established via an atomic compare-and-swap loop. |
| 48 | + * |
| 49 | + * Example: |
| 50 | + * \code{.cpp} |
| 51 | + * template <typename T> |
| 52 | + * struct custom_plus { |
| 53 | + * __device__ T operator()(T const& lhs, T const& rhs) const noexcept { |
| 54 | + * return lhs + rhs; |
| 55 | + * } |
| 56 | + * }; |
| 57 | + * |
| 58 | + * template <typename T> |
| 59 | + * struct custom_plus_sync { |
| 60 | + * template <cuda::thread_scope Scope> |
| 61 | + * __device__ T operator()(cuda::atomic<T, Scope>& lhs, T const& rhs) const noexcept { |
| 62 | + * return lhs.fetch_add(rhs) + rhs; |
| 63 | + * } |
| 64 | + * }; |
| 65 | + * |
| 66 | + * int main() { |
| 67 | + * cuco::identity_value<int> identity{0}; // define the identity value for the given reduction operation, i.e., op(identity, x) == x |
| 68 | + * |
| 69 | + * auto f1 = cuco::reduction_functor<custom_plus<int>, int>(identity); // synchronized via CAS-loop |
| 70 | + * auto f2 = cuco::reduction_functor<custom_plus_sync<int>, int>(identity); // implicitly synchronized |
| 71 | + * |
| 72 | + * auto custom_plus_lambda = [] __device__ (int lhs, int rhs) noexcept { return lhs + rhs; }; |
| 73 | + * auto f3 = cuco::reduction_functor<decltype(custom_plus_lambda), int>(identity, custom_plus_lambda); |
| 74 | + * } |
| 75 | + * \endcode |
| 76 | + * |
| 77 | + * @tparam Func The user-defined reduction functor |
| 78 | + * @tparam Value The value type used for reduction |
| 79 | + */ |
| 80 | +template <typename Func, typename Value> |
| 81 | +class reduction_functor : detail::reduction_functor_base { |
| 82 | + public: |
| 83 | + using value_type = Value; |
| 84 | + |
| 85 | + reduction_functor(cuco::identity_value<Value> identity, Func functor = Func{}) noexcept : identity_(identity), functor_(functor) {} |
| 86 | + |
| 87 | + template <cuda::thread_scope Scope> |
| 88 | + __device__ value_type operator()(cuda::atomic<value_type, Scope>& lhs, value_type const& rhs) const noexcept |
| 89 | + { |
| 90 | + if constexpr (uses_external_sync()) { |
| 91 | + value_type old = lhs.load(cuda::memory_order_relaxed); |
| 92 | + value_type desired; |
| 93 | + |
| 94 | + do { |
| 95 | + desired = functor_(old, rhs); |
| 96 | + } while (!lhs.compare_exchange_weak(old, desired, cuda::memory_order_release, cuda::memory_order_relaxed)); |
| 97 | + |
| 98 | + return desired; |
| 99 | + } else { |
| 100 | + return functor_(lhs, rhs); |
| 101 | + } |
| 102 | + } |
| 103 | + |
| 104 | + __host__ __device__ value_type identity() const noexcept { |
| 105 | + return identity_.value(); |
| 106 | + } |
| 107 | + |
| 108 | + __host__ __device__ static constexpr bool uses_external_sync() noexcept { |
| 109 | + return !atomic_invocable_ || naive_invocable_; |
| 110 | + } |
| 111 | + |
| 112 | + private: |
| 113 | + cuco::identity_value<value_type> identity_; |
| 114 | + Func functor_; |
| 115 | + static constexpr bool naive_invocable_ = std::is_invocable_r<value_type, Func, value_type, value_type>::value; |
| 116 | + static constexpr bool atomic_invocable_ = |
| 117 | + std::is_invocable_r<value_type, Func, cuda::atomic<value_type, cuda::thread_scope_system>&, value_type>::value || |
| 118 | + std::is_invocable_r<value_type, Func, cuda::atomic<value_type, cuda::thread_scope_device>&, value_type>::value || |
| 119 | + std::is_invocable_r<value_type, Func, cuda::atomic<value_type, cuda::thread_scope_block>&, value_type>::value || |
| 120 | + std::is_invocable_r<value_type, Func, cuda::atomic<value_type, cuda::thread_scope_thread>&, value_type>::value; |
| 121 | + |
| 122 | + static_assert(atomic_invocable_ || naive_invocable_, "Invalid operator signature."); |
| 123 | +}; |
| 124 | + |
| 125 | +/** |
| 126 | + * @brief Synchronized `+` reduction functor. |
| 127 | + * |
| 128 | + * @tparam T The value type used for reduction |
| 129 | + */ |
| 130 | +template <typename T> |
| 131 | +auto reduce_add() { return reduction_functor(identity_value<T>{0}, detail::reduce_add_impl<T>{}); }; |
| 132 | + |
| 133 | +/** |
| 134 | + * @brief Synchronized `min` reduction functor. |
| 135 | + * |
| 136 | + * @tparam T The value type used for reduction |
| 137 | + */ |
| 138 | +template <typename T> |
| 139 | +auto reduce_min() { return reduction_functor(identity_value{cuda::std::numeric_limits<T>::max()}, detail::reduce_min_impl<T>{}); }; |
| 140 | + |
| 141 | +/** |
| 142 | + * @brief Synchronized `max` reduction functor. |
| 143 | + * |
| 144 | + * @tparam T The value type used for reduction |
| 145 | + */ |
| 146 | +template <typename T> |
| 147 | +auto reduce_max() { return reduction_functor(identity_value{cuda::std::numeric_limits<T>::lowest()}, detail::reduce_max_impl<T>{}); }; |
| 148 | + |
| 149 | +/** |
| 150 | + * @brief Synchronized `count` reduction functor. |
| 151 | + * |
| 152 | + * @tparam T The value type used for reduction |
| 153 | + */ |
| 154 | +template <typename T> |
| 155 | +auto reduce_count() { return reduction_functor(identity_value<T>{0}, detail::reduce_count_impl<T>{}); }; |
| 156 | + |
| 157 | +} // namespace cuco |
0 commit comments