Skip to content

Commit

Permalink
Add pluggable_quicksort and make it default parallel sort impl (#29)
Browse files Browse the repository at this point in the history
Add `pluggable_quicksort` and make it default parallel sort impl

Retain `pluggable_mergesort` for now.
  • Loading branch information
alugowski authored Jan 22, 2024
1 parent 0b888b0 commit dd6e324
Show file tree
Hide file tree
Showing 6 changed files with 320 additions and 65 deletions.
8 changes: 4 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ All in `std::` namespace.
### Other
* [`poolstl::iota_iter`](include/poolstl/iota_iter.hpp) - Iterate over integers. Same as iterating over output of [`std::iota`](https://en.cppreference.com/w/cpp/algorithm/iota) but without materializing anything. Iterator version of [`std::ranges::iota_view`](https://en.cppreference.com/w/cpp/ranges/iota_view).
* `poolstl::for_each_chunk` - Like `std::for_each`, but explicitly splits the input range into chunks then exposes the chunked parallelism. A user-specified chunk constructor is called for each parallel chunk then its output is passed to each loop iteration. Useful for workloads that need an expensive workspace that can be reused between iterations, but not simultaneously by all iterations in parallel.
* `poolstl::pluggable_sort` - Like `std::sort`, but allows specification of sequential sort and merge methods. To parallelize [pdqsort](https://github.com/orlp/pdqsort): `pluggable_sort(par, v.begin(), v.end(), pdqsort)`.
* `poolstl::pluggable_sort` - Like `std::sort`, but allows specification of sequential sort method. To parallelize [pdqsort](https://github.com/orlp/pdqsort): `pluggable_sort(par, v.begin(), v.end(), pdqsort)`.
## Usage
Expand Down Expand Up @@ -197,9 +197,9 @@ for_each()/real_time 94.6 ms
for_each(poolstl::par)/real_time 18.7 ms 0.044 ms 36
for_each(std::execution::par)/real_time 15.3 ms 12.9 ms 46
sort()/real_time 603 ms 602 ms 1
sort(poolstl::par)/real_time 146 ms 0.667 ms 5
sort(std::execution::par)/real_time 121 ms 95.1 ms 6
pluggable_sort(poolstl::par, ..., pdqsort)/real_time 97.7 ms 0.519 ms 7
sort(poolstl::par)/real_time 137 ms 11.8 ms 5
sort(std::execution::par)/real_time 113 ms 102 ms 6
pluggable_sort(poolstl::par, ..., pdqsort)/real_time 91.8 ms 11.9 ms 7
transform()/real_time 95.0 ms 94.9 ms 7
transform(poolstl::par)/real_time 17.4 ms 0.037 ms 38
transform(std::execution::par)/real_time 15.3 ms 13.2 ms 45
Expand Down
14 changes: 10 additions & 4 deletions benchmark/algorithm_bench.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ BENCHMARK(sort<std_par>)->Name("sort(std::execution::par)")->UseRealTime();
////////////////////////////////

template <class ExecPolicy, int which_impl>
void pluggable_sort_pdq(benchmark::State& state) {
void pluggable_sort(benchmark::State& state) {
auto source = random_vector<int>(arr_length / 10);
// auto source = random_vector<int>(arr_length);

Expand All @@ -134,16 +134,22 @@ void pluggable_sort_pdq(benchmark::State& state) {
if constexpr (which_impl == 1) {
poolstl::pluggable_sort(policy<ExecPolicy>::get(), values.begin(), values.end(), pdqsort);
} else if constexpr (which_impl == 2) {
poolstl::pluggable_sort(policy<ExecPolicy>::get(), values.begin(), values.end(), pdqsort, adapted_pipm_inplace_merge);
poolstl::pluggable_mergesort(policy<ExecPolicy>::get(), values.begin(), values.end(), pdqsort);
} else if constexpr (which_impl == 3) {
poolstl::pluggable_mergesort(policy<ExecPolicy>::get(), values.begin(), values.end(), pdqsort, adapted_pipm_inplace_merge);
} else if constexpr (which_impl == 4) {
// pluggable_sort delegates to this, so essentially same as which_impl==1
poolstl::pluggable_quicksort(policy<ExecPolicy>::get(), values.begin(), values.end(), pdqsort);
}

benchmark::DoNotOptimize(values);
benchmark::ClobberMemory();
}
}

BENCHMARK(pluggable_sort_pdq<poolstl_par, 1>)->Name("pluggable_sort(poolstl::par, ..., pdqsort)")->UseRealTime(); // uses pdqsort and std::inplace_merge (O(n) extra memory)
BENCHMARK(pluggable_sort_pdq<poolstl_par, 2>)->Name("pluggable_sort(poolstl::par, ..., pdqsort, pipm_merge)")->UseRealTime(); // uses pdqsort and adapted_pipm_inplace_merge (O(1) extra memory)
BENCHMARK(pluggable_sort<poolstl_par, 1>)->Name("pluggable_sort(poolstl::par, ..., pdqsort)")->UseRealTime(); // uses pdqsort
//BENCHMARK(pluggable_sort<poolstl_par, 2>)->Name("pluggable_mergesort(poolstl::par, ..., pdqsort)")->UseRealTime(); // uses pdqsort and std::inplace_merge (O(n) extra memory)
//BENCHMARK(pluggable_sort<poolstl_par, 3>)->Name("pluggable_mergesort(poolstl::par, ..., pdqsort, pipm_merge)")->UseRealTime(); // uses pdqsort and adapted_pipm_inplace_merge (slower, but O(1) extra memory)

////////////////////////////////

Expand Down
135 changes: 123 additions & 12 deletions include/poolstl/algorithm
Original file line number Diff line number Diff line change
Expand Up @@ -221,8 +221,11 @@ namespace std {
return;
}

poolstl::internal::parallel_sort(std::forward<ExecPolicy>(policy), first, last, comp,
std::sort<RandIt, Compare>, std::inplace_merge<RandIt, Compare>);
poolstl::internal::parallel_quicksort(std::forward<ExecPolicy>(policy), first, last, comp,
std::sort<RandIt, Compare>,
std::partition<RandIt, poolstl::internal::pivot_predicate<Compare,
typename std::iterator_traits<RandIt>::value_type>>,
poolstl::internal::quicksort_pivot<RandIt>);
}

/**
Expand All @@ -248,8 +251,11 @@ namespace std {
return;
}

poolstl::internal::parallel_sort(std::forward<ExecPolicy>(policy), first, last, comp,
std::stable_sort<RandIt, Compare>, std::inplace_merge<RandIt, Compare>);
poolstl::internal::parallel_quicksort(std::forward<ExecPolicy>(policy), first, last, comp,
std::stable_sort<RandIt, Compare>,
std::stable_partition<RandIt, poolstl::internal::pivot_predicate<Compare,
typename std::iterator_traits<RandIt>::value_type>>,
poolstl::internal::quicksort_pivot<RandIt>);
}

/**
Expand Down Expand Up @@ -374,37 +380,142 @@ namespace poolstl {
/**
* NOTE: Iterators are expected to be random access.
*
* Like `std::sort`, but allows specifying the sequential sort and merge methods. These methods must have the
* same signature as the comparator versions of `std::sort` and `std::inplace_merge`, respectively.
* Like `std::sort`, but allows specifying the sequential sort method, which must have the
* same signature as the comparator version of `std::sort`.
*
* Implemented as a high-level quicksort that delegates to `sort_func`, in parallel, once the range has been
* sufficiently partitioned.
*/
template <class ExecPolicy, class RandIt, class Compare>
poolstl::internal::enable_if_poolstl_policy<ExecPolicy, void>
pluggable_sort(ExecPolicy &&policy, RandIt first, RandIt last, Compare comp,
void (sort_func)(RandIt, RandIt, Compare) = std::sort,
void (merge_func)(RandIt, RandIt, RandIt, Compare) = std::inplace_merge) {
void (sort_func)(RandIt, RandIt, Compare) = std::sort) {
if (poolstl::internal::is_seq<ExecPolicy>(policy)) {
sort_func(first, last, comp);
return;
}

poolstl::internal::parallel_sort(std::forward<ExecPolicy>(policy), first, last, comp, sort_func, merge_func);
poolstl::internal::parallel_quicksort(std::forward<ExecPolicy>(policy), first, last, comp, sort_func,
std::partition<RandIt, poolstl::internal::pivot_predicate<Compare,
typename std::iterator_traits<RandIt>::value_type>>,
poolstl::internal::quicksort_pivot<RandIt>);
}

/**
* NOTE: Iterators are expected to be random access.
*
* Like `std::sort`, but allows specifying the sequential sort and merge methods. These methods must have the
* same signature as the comparator versions of `std::sort` and `std::inplace_merge`, respectively.
* Like `std::sort`, but allows specifying the sequential sort method, which must have the
* same signature as the comparator version of `std::sort`.
*
* Implemented as a parallel high-level quicksort that delegates to `sort_func` once the range has been
* sufficiently partitioned.
*/
template <class ExecPolicy, class RandIt>
poolstl::internal::enable_if_poolstl_policy<ExecPolicy, void>
pluggable_sort(ExecPolicy &&policy, RandIt first, RandIt last,
void (sort_func)(RandIt, RandIt,
std::less<typename std::iterator_traits<RandIt>::value_type>) = std::sort){
using T = typename std::iterator_traits<RandIt>::value_type;
pluggable_sort(std::forward<ExecPolicy>(policy), first, last, std::less<T>(), sort_func);
}

/**
* NOTE: Iterators are expected to be random access.
*
* Parallel merge sort.
*
* @param comp Comparator.
* @param sort_func Sequential sort method. Must have the same signature as the comparator version of `std::sort`.
* @param merge_func Sequential merge method. Must have the same signature as `std::inplace_merge`.
*/
template <class ExecPolicy, class RandIt, class Compare>
poolstl::internal::enable_if_poolstl_policy<ExecPolicy, void>
pluggable_mergesort(ExecPolicy &&policy, RandIt first, RandIt last, Compare comp,
void (sort_func)(RandIt, RandIt, Compare) = std::sort,
void (merge_func)(RandIt, RandIt, RandIt, Compare) = std::inplace_merge) {
if (poolstl::internal::is_seq<ExecPolicy>(policy)) {
sort_func(first, last, comp);
return;
}

poolstl::internal::parallel_mergesort(std::forward<ExecPolicy>(policy),
first, last, comp, sort_func, merge_func);
}

/**
* NOTE: Iterators are expected to be random access.
*
* Parallel merge sort.
*
* Uses `std::less` comparator.
*
* @param sort_func Sequential sort method. Must have the same signature as the comparator version of `std::sort`.
* @param merge_func Sequential merge method. Must have the same signature as `std::inplace_merge`.
*/
template <class ExecPolicy, class RandIt>
poolstl::internal::enable_if_poolstl_policy<ExecPolicy, void>
pluggable_mergesort(ExecPolicy &&policy, RandIt first, RandIt last,
void (sort_func)(RandIt, RandIt,
std::less<typename std::iterator_traits<RandIt>::value_type>) = std::sort,
void (merge_func)(RandIt, RandIt, RandIt,
std::less<typename std::iterator_traits<RandIt>::value_type>) = std::inplace_merge){
using T = typename std::iterator_traits<RandIt>::value_type;
pluggable_sort(std::forward<ExecPolicy>(policy), first, last, std::less<T>(), sort_func, merge_func);
pluggable_mergesort(std::forward<ExecPolicy>(policy), first, last, std::less<T>(), sort_func, merge_func);
}

/**
* NOTE: Iterators are expected to be random access.
*
* Parallel quicksort that allows specifying the sequential sort and partition methods.
*
* @param comp Comparator.
* @param sort_func Sequential sort method to use once range is sufficiently partitioned. Must have the same
* signature as the comparator version of `std::sort`.
* @param part_func Sequential partition method. Must have the same signature as `std::partition`.
* @param pivot_func Method that identifies the pivot element
*/
template <class ExecPolicy, class RandIt, class Compare>
poolstl::internal::enable_if_poolstl_policy<ExecPolicy, void>
pluggable_quicksort(ExecPolicy &&policy, RandIt first, RandIt last, Compare comp,
void (sort_func)(RandIt, RandIt, Compare) = std::sort,
RandIt (part_func)(RandIt, RandIt, poolstl::internal::pivot_predicate<Compare,
typename std::iterator_traits<RandIt>::value_type>) = std::partition,
typename std::iterator_traits<RandIt>::value_type (pivot_func)(RandIt, RandIt) =
poolstl::internal::quicksort_pivot) {
if (poolstl::internal::is_seq<ExecPolicy>(policy)) {
sort_func(first, last, comp);
return;
}

poolstl::internal::parallel_quicksort(std::forward<ExecPolicy>(policy),
first, last, comp, sort_func, part_func, pivot_func);
}

/**
* NOTE: Iterators are expected to be random access.
*
* Parallel quicksort that allows specifying the sequential sort and partition methods.
*
* Uses `std::less` comparator.
*
* @param sort_func Sequential sort method to use once range is sufficiently partitioned. Must have the same
* signature as the comparator version of `std::sort`.
* @param part_func Sequential partition method. Must have the same signature as `std::partition`.
* @param pivot_func Method that identifies the pivot element
*/
template <class ExecPolicy, class RandIt>
poolstl::internal::enable_if_poolstl_policy<ExecPolicy, void>
pluggable_quicksort(ExecPolicy &&policy, RandIt first, RandIt last,
void (sort_func)(RandIt, RandIt,
std::less<typename std::iterator_traits<RandIt>::value_type>) = std::sort,
RandIt (part_func)(RandIt, RandIt, poolstl::internal::pivot_predicate<
std::less<typename std::iterator_traits<RandIt>::value_type>,
typename std::iterator_traits<RandIt>::value_type>) = std::partition,
typename std::iterator_traits<RandIt>::value_type (pivot_func)(RandIt, RandIt) =
poolstl::internal::quicksort_pivot) {
using T = typename std::iterator_traits<RandIt>::value_type;
pluggable_quicksort(std::forward<ExecPolicy>(policy), first, last, std::less<T>(),
sort_func, part_func, pivot_func);
}
}

Expand Down
Loading

0 comments on commit dd6e324

Please sign in to comment.