partition.h
Go to the documentation of this file.
// -*- C++ -*-
// Copyright (C) 2007-2025 Free Software Foundation, Inc.
//
// This file is part of the GNU ISO C++ Library. This library is free
// software; you can redistribute it and/or modify it under the terms
// of the GNU General Public License as published by the Free Software
// Foundation; either version 3, or (at your option) any later
// version.
// This library is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// General Public License for more details.
// Under Section 7 of GPL version 3, you are granted additional
// permissions described in the GCC Runtime Library Exception, version
// 3.1, as published by the Free Software Foundation.
// You should have received a copy of the GNU General Public License and
// a copy of the GCC Runtime Library Exception along with this program;
// see the files COPYING3 and COPYING.RUNTIME respectively. If not, see
// <http://www.gnu.org/licenses/>.
/** @file parallel/partition.h
* @brief Parallel implementation of std::partition(),
* std::nth_element(), and std::partial_sort().
* This file is a GNU parallel extension to the Standard C++ Library.
*/
// Written by Johannes Singler and Felix Putze.
#ifndef _GLIBCXX_PARALLEL_PARTITION_H
#define _GLIBCXX_PARALLEL_PARTITION_H 1
#include <parallel/basic_iterator.h>
#include <parallel/sort.h>
#include <parallel/random_number.h>
#include <bits/stl_algo.h>
#include <parallel/parallel.h>
/** @brief Decide whether to declare certain variables volatile. */
#define _GLIBCXX_VOLATILE volatile
namespace __gnu_parallel
{
/** @brief Parallel implementation of std::partition.
* @param __begin Begin iterator of input sequence to split.
* @param __end End iterator of input sequence to split.
* @param __pred Partition predicate, possibly including some kind
* of pivot.
* @param __num_threads Maximum number of threads to use for this task.
* @return Number of elements not fulfilling the predicate. */
template<typename _RAIter, typename _Predicate>
typename std::iterator_traits<_RAIter>::difference_type
__parallel_partition(_RAIter __begin, _RAIter __end,
_Predicate __pred, _ThreadIndex __num_threads)
{
typedef std::iterator_traits<_RAIter> _TraitsType;
typedef typename _TraitsType::value_type _ValueType;
typedef typename _TraitsType::difference_type _DifferenceType;
_DifferenceType __n = __end - __begin;
_GLIBCXX_CALL(__n)
const _Settings& __s = _Settings::get();
// shared
_GLIBCXX_VOLATILE _DifferenceType __left = 0, __right = __n - 1,
__dist = __n,
__leftover_left, __leftover_right,
__leftnew, __rightnew;
// just 0 or 1, but int to allow atomic operations
int* __reserved_left = 0, * __reserved_right = 0;
_DifferenceType __chunk_size = __s.partition_chunk_size;
//at least two chunks per thread
if (__dist >= 2 * __num_threads * __chunk_size)
# pragma omp parallel num_threads(__num_threads)
{
# pragma omp single
{
__num_threads = omp_get_num_threads();
__reserved_left = new int[__num_threads];
__reserved_right = new int[__num_threads];
if (__s.partition_chunk_share > 0.0)
__chunk_size = std::max<_DifferenceType>
(__s.partition_chunk_size, (double)__n
* __s.partition_chunk_share / (double)__num_threads);
else
__chunk_size = __s.partition_chunk_size;
}
while (__dist >= 2 * __num_threads * __chunk_size)
{
# pragma omp single
{
_DifferenceType __num_chunks = __dist / __chunk_size;
for (_ThreadIndex __r = 0; __r < __num_threads; ++__r)
{
__reserved_left [__r] = 0; // false
__reserved_right[__r] = 0; // false
}
__leftover_left = 0;
__leftover_right = 0;
} //implicit barrier
// Private.
_DifferenceType __thread_left, __thread_left_border,
__thread_right, __thread_right_border;
__thread_left = __left + 1;
// Just to satisfy the condition below.
__thread_left_border = __thread_left - 1;
__thread_right = __n - 1;
// Just to satisfy the condition below.
__thread_right_border = __thread_right + 1;
bool __iam_finished = false;
while (!__iam_finished)
{
if (__thread_left > __thread_left_border)
{
_DifferenceType __former_dist =
__fetch_and_add(&__dist, -__chunk_size);
if (__former_dist < __chunk_size)
{
__fetch_and_add(&__dist, __chunk_size);
__iam_finished = true;
break;
}
else
{
__thread_left =
__fetch_and_add(&__left, __chunk_size);
__thread_left_border =
__thread_left + (__chunk_size - 1);
}
}
if (__thread_right < __thread_right_border)
{
_DifferenceType __former_dist =
__fetch_and_add(&__dist, -__chunk_size);
if (__former_dist < __chunk_size)
{
__fetch_and_add(&__dist, __chunk_size);
__iam_finished = true;
break;
}
else
{
__thread_right =
__fetch_and_add(&__right, -__chunk_size);
__thread_right_border =
__thread_right - (__chunk_size - 1);
}
}
// Swap as usual.
while (__thread_left < __thread_right)
{
while (__pred(__begin[__thread_left])
&& __thread_left <= __thread_left_border)
++__thread_left;
while (!__pred(__begin[__thread_right])
&& __thread_right >= __thread_right_border)
--__thread_right;
if (__thread_left > __thread_left_border
|| __thread_right < __thread_right_border)
// Fetch new chunk(__s).
break;
std::iter_swap(__begin + __thread_left,
__begin + __thread_right);
++__thread_left;
--__thread_right;
}
}
// Now swap the leftover chunks to the right places.
if (__thread_left <= __thread_left_border)
# pragma omp atomic
++__leftover_left;
if (__thread_right >= __thread_right_border)
# pragma omp atomic
++__leftover_right;
# pragma omp barrier
_DifferenceType
__leftold = __left,
__leftnew = __left - __leftover_left * __chunk_size,
__rightold = __right,
__rightnew = __right + __leftover_right * __chunk_size;
// <=> __thread_left_border + (__chunk_size - 1) >= __leftnew
if (__thread_left <= __thread_left_border
&& __thread_left_border >= __leftnew)
{
// Chunk already in place, reserve spot.
__reserved_left[(__left - (__thread_left_border + 1))
/ __chunk_size] = 1;
}
// <=> __thread_right_border - (__chunk_size - 1) <= __rightnew
if (__thread_right >= __thread_right_border
&& __thread_right_border <= __rightnew)
{
// Chunk already in place, reserve spot.
__reserved_right[((__thread_right_border - 1) - __right)
/ __chunk_size] = 1;
}
# pragma omp barrier
if (__thread_left <= __thread_left_border
&& __thread_left_border < __leftnew)
{
// Find spot and swap.
_DifferenceType __swapstart = -1;
for (int __r = 0; __r < __leftover_left; ++__r)
if (__reserved_left[__r] == 0
&& __compare_and_swap(&(__reserved_left[__r]), 0, 1))
{
__swapstart = __leftold - (__r + 1) * __chunk_size;
break;
}
#if _GLIBCXX_PARALLEL_ASSERTIONS
_GLIBCXX_PARALLEL_ASSERT(__swapstart != -1);
#endif
std::swap_ranges(__begin + __thread_left_border
- (__chunk_size - 1),
__begin + __thread_left_border + 1,
__begin + __swapstart);
}
if (__thread_right >= __thread_right_border
&& __thread_right_border > __rightnew)
{
// Find spot and swap
_DifferenceType __swapstart = -1;
for (int __r = 0; __r < __leftover_right; ++__r)
if (__reserved_right[__r] == 0
&& __compare_and_swap(&(__reserved_right[__r]), 0, 1))
{
__swapstart = __rightold + __r * __chunk_size + 1;
break;
}
#if _GLIBCXX_PARALLEL_ASSERTIONS
_GLIBCXX_PARALLEL_ASSERT(__swapstart != -1);
#endif
std::swap_ranges(__begin + __thread_right_border,
__begin + __thread_right_border
+ __chunk_size, __begin + __swapstart);
}
#if _GLIBCXX_PARALLEL_ASSERTIONS
# pragma omp barrier
# pragma omp single
{
for (_DifferenceType __r = 0; __r < __leftover_left; ++__r)
_GLIBCXX_PARALLEL_ASSERT(__reserved_left[__r] == 1);
for (_DifferenceType __r = 0; __r < __leftover_right; ++__r)
_GLIBCXX_PARALLEL_ASSERT(__reserved_right[__r] == 1);
}
#endif
__left = __leftnew;
__right = __rightnew;
__dist = __right - __left + 1;
}
# pragma omp flush(__left, __right)
} // end "recursion" //parallel
_DifferenceType __final_left = __left, __final_right = __right;
while (__final_left < __final_right)
{
// Go right until key is geq than pivot.
while (__pred(__begin[__final_left])
&& __final_left < __final_right)
++__final_left;
// Go left until key is less than pivot.
while (!__pred(__begin[__final_right])
&& __final_left < __final_right)
--__final_right;
if (__final_left == __final_right)
break;
std::iter_swap(__begin + __final_left, __begin + __final_right);
++__final_left;
--__final_right;
}
// All elements on the left side are < piv, all elements on the
// right are >= piv
delete[] __reserved_left;
delete[] __reserved_right;
// Element "between" __final_left and __final_right might not have
// been regarded yet
if (__final_left < __n && !__pred(__begin[__final_left]))
// Really swapped.
return __final_left;
else
return __final_left + 1;
}
/**
* @brief Parallel implementation of std::nth_element().
* @param __begin Begin iterator of input sequence.
* @param __nth _Iterator of element that must be in position afterwards.
* @param __end End iterator of input sequence.
* @param __comp Comparator.
*/
template<typename _RAIter, typename _Compare>
void
__parallel_nth_element(_RAIter __begin, _RAIter __nth,
_RAIter __end, _Compare __comp)
{
typedef std::iterator_traits<_RAIter> _TraitsType;
typedef typename _TraitsType::value_type _ValueType;
typedef typename _TraitsType::difference_type _DifferenceType;
_GLIBCXX_CALL(__end - __begin)
_RAIter __split;
_RandomNumber __rng;
const _Settings& __s = _Settings::get();
_DifferenceType __minimum_length = std::max<_DifferenceType>(2,
std::max(__s.nth_element_minimal_n, __s.partition_minimal_n));
// Break if input range to small.
while (static_cast<_SequenceIndex>(__end - __begin) >= __minimum_length)
{
_DifferenceType __n = __end - __begin;
_RAIter __pivot_pos = __begin + __rng(__n);
// Swap __pivot_pos value to end.
if (__pivot_pos != (__end - 1))
std::iter_swap(__pivot_pos, __end - 1);
__pivot_pos = __end - 1;
// _Compare must have first_value_type, second_value_type,
// result_type
// _Compare ==
// __gnu_parallel::_Lexicographic<S, int,
// __gnu_parallel::_Less<S, S> >
// __pivot_pos == std::pair<S, int>*
__gnu_parallel::__binder2nd<_Compare, _ValueType, _ValueType, bool>
__pred(__comp, *__pivot_pos);
// Divide, leave pivot unchanged in last place.
_RAIter __split_pos1, __split_pos2;
__split_pos1 = __begin + __parallel_partition(__begin, __end - 1,
__pred,
__get_max_threads());
// Left side: < __pivot_pos; __right side: >= __pivot_pos
// Swap pivot back to middle.
if (__split_pos1 != __pivot_pos)
std::iter_swap(__split_pos1, __pivot_pos);
__pivot_pos = __split_pos1;
// In case all elements are equal, __split_pos1 == 0
if ((__split_pos1 + 1 - __begin) < (__n >> 7)
|| (__end - __split_pos1) < (__n >> 7))
{
// Very unequal split, one part smaller than one 128th
// elements not strictly larger than the pivot.
__gnu_parallel::__unary_negate<__gnu_parallel::
__binder1st<_Compare, _ValueType,
_ValueType, bool>, _ValueType>
__pred(__gnu_parallel::__binder1st<_Compare, _ValueType,
_ValueType, bool>(__comp, *__pivot_pos));
// Find other end of pivot-equal range.
__split_pos2 = __gnu_sequential::partition(__split_pos1 + 1,
__end, __pred);
}
else
// Only skip the pivot.
__split_pos2 = __split_pos1 + 1;
// Compare iterators.
if (__split_pos2 <= __nth)
__begin = __split_pos2;
else if (__nth < __split_pos1)
__end = __split_pos1;
else
break;
}
// Only at most _Settings::partition_minimal_n __elements __left.
__gnu_sequential::nth_element(__begin, __nth, __end, __comp);
}
/** @brief Parallel implementation of std::partial_sort().
* @param __begin Begin iterator of input sequence.
* @param __middle Sort until this position.
* @param __end End iterator of input sequence.
* @param __comp Comparator. */
template<typename _RAIter, typename _Compare>
void
__parallel_partial_sort(_RAIter __begin,
_RAIter __middle,
_RAIter __end, _Compare __comp)
{
__parallel_nth_element(__begin, __middle, __end, __comp);
std::sort(__begin, __middle, __comp);
}
} //namespace __gnu_parallel
#undef _GLIBCXX_VOLATILE
#endif /* _GLIBCXX_PARALLEL_PARTITION_H */
stl_algo.h
_GLIBCXX_CALL
#define _GLIBCXX_CALL(__n)
Macro to produce log message when entering a function.
Definition compiletime_settings.h:44
random_number.h
Random number generator based on the Mersenne twister. This file is a GNU parallel extension to the S...
parallel.h
End-user include file. Provides advanced settings and tuning options. This file is a GNU parallel ext...
_GLIBCXX_VOLATILE
#define _GLIBCXX_VOLATILE
Decide whether to declare certain variables volatile.
Definition partition.h:43
sort.h
Parallel sorting algorithm switch. This file is a GNU parallel extension to the Standard C++ Library.
basic_iterator.h
Includes the original header files concerned with iterators except for stream iterators....
std::max
constexpr const _Tp & max(const _Tp &, const _Tp &)
This does what you think it does.
Definition stl_algobase.h:258
__gnu_parallel
GNU parallel code for public use.
Definition balanced_quicksort.h:62
__gnu_parallel::_ThreadIndex
uint16_t _ThreadIndex
Unsigned integer to index a thread number. The maximum thread number (for each processor) must fit in...
Definition types.h:123
__gnu_parallel::__parallel_nth_element
void __parallel_nth_element(_RAIter __begin, _RAIter __nth, _RAIter __end, _Compare __comp)
Parallel implementation of std::nth_element().
Definition partition.h:332
__gnu_parallel::_SequenceIndex
uint64_t _SequenceIndex
Unsigned integer to index __elements. The total number of elements for each algorithm must fit into t...
Definition types.h:117
__gnu_parallel::__fetch_and_add
_Tp __fetch_and_add(volatile _Tp *__ptr, _Tp __addend)
Add a value to a variable, atomically.
Definition parallel/compatibility.h:74
__gnu_parallel::__parallel_partial_sort
void __parallel_partial_sort(_RAIter __begin, _RAIter __middle, _RAIter __end, _Compare __comp)
Parallel implementation of std::partial_sort().
Definition partition.h:422
__gnu_parallel::__parallel_partition
std::iterator_traits< _RAIter >::difference_type __parallel_partition(_RAIter __begin, _RAIter __end, _Predicate __pred, _ThreadIndex __num_threads)
Parallel implementation of std::partition.
Definition partition.h:56
__gnu_parallel::__compare_and_swap
bool __compare_and_swap(volatile _Tp *__ptr, _Tp __comparand, _Tp __replacement)
Compare-and-swap.
Definition parallel/compatibility.h:108
std::iterator_traits
Traits class for iterators.
Definition stl_iterator_base_types.h:180
__gnu_parallel::__unary_negate
Similar to std::unary_negate, but giving the argument types explicitly.
Definition base.h:177
__gnu_parallel::__binder1st
Similar to std::binder1st, but giving the argument types explicitly.
Definition base.h:196
__gnu_parallel::__binder2nd
Similar to std::binder2nd, but giving the argument types explicitly.
Definition base.h:224
__gnu_parallel::_RandomNumber
Random number generator, based on the Mersenne twister.
Definition random_number.h:46
__gnu_parallel::_Settings
class _Settings Run-time settings for the parallel mode including all tunable parameters.
Definition settings.h:126
__gnu_parallel::_Settings::nth_element_minimal_n
_SequenceIndex nth_element_minimal_n
Minimal input size for nth_element.
Definition settings.h:192
__gnu_parallel::_Settings::partition_chunk_share
double partition_chunk_share
Chunk size for partition, relative to input size. If > 0.0, this value overrides partition_chunk_size...
Definition settings.h:199
__gnu_parallel::_Settings::partition_chunk_size
_SequenceIndex partition_chunk_size
Chunk size for partition.
Definition settings.h:195
__gnu_parallel::_Settings::partition_minimal_n
_SequenceIndex partition_minimal_n
Minimal input size for partition.
Definition settings.h:202
__gnu_parallel::_Settings::get
static const _Settings & get()
Get the global settings.