pdqsort.hpp 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345
  1. //////////////////////////////////////////////////////////////////////////////
  2. //
  3. // (C) Copyright Orson Peters 2017.
  4. // (C) Copyright Ion Gaztanaga 2017-2018.
  5. // Distributed under the Boost Software License, Version 1.0.
  6. // (See accompanying file LICENSE_1_0.txt or copy at
  7. // http://www.boost.org/LICENSE_1_0.txt)
  8. //
  9. // See http://www.boost.org/libs/move for documentation.
  10. //
  11. //////////////////////////////////////////////////////////////////////////////
  12. //
  13. // This implementation of Pattern-defeating quicksort (pdqsort) was written
  14. // by Orson Peters, and discussed in the Boost mailing list:
  15. // http://boost.2283326.n4.nabble.com/sort-pdqsort-td4691031.html
  16. //
  17. // This implementation is the adaptation by Ion Gaztanaga of code originally in GitHub
  18. // with permission from the author to relicense it under the Boost Software License
  19. // (see the Boost mailing list for details).
  20. //
  21. // The original copyright statement is pasted here for completeness:
  22. //
  23. // pdqsort.h - Pattern-defeating quicksort.
  24. // Copyright (c) 2015 Orson Peters
  25. // This software is provided 'as-is', without any express or implied warranty. In no event will the
  26. // authors be held liable for any damages arising from the use of this software.
  27. // Permission is granted to anyone to use this software for any purpose, including commercial
  28. // applications, and to alter it and redistribute it freely, subject to the following restrictions:
  29. // 1. The origin of this software must not be misrepresented; you must not claim that you wrote the
  30. // original software. If you use this software in a product, an acknowledgment in the product
  31. // documentation would be appreciated but is not required.
  32. // 2. Altered source versions must be plainly marked as such, and must not be misrepresented as
  33. // being the original software.
  34. // 3. This notice may not be removed or altered from any source distribution.
  35. //
  36. //////////////////////////////////////////////////////////////////////////////
  37. #ifndef BOOST_MOVE_ALGO_PDQSORT_HPP
  38. #define BOOST_MOVE_ALGO_PDQSORT_HPP
  39. #ifndef BOOST_CONFIG_HPP
  40. # include <boost/config.hpp>
  41. #endif
  42. #
  43. #if defined(BOOST_HAS_PRAGMA_ONCE)
  44. # pragma once
  45. #endif
  46. #include <boost/move/detail/config_begin.hpp>
  47. #include <boost/move/detail/workaround.hpp>
  48. #include <boost/move/utility_core.hpp>
  49. #include <boost/move/algo/detail/insertion_sort.hpp>
  50. #include <boost/move/algo/detail/heap_sort.hpp>
  51. #include <boost/move/detail/iterator_traits.hpp>
  52. #include <boost/move/adl_move_swap.hpp>
  53. #include <cstddef>
  54. #if defined(BOOST_CLANG) || (defined(BOOST_GCC) && (BOOST_GCC >= 40600))
  55. #pragma GCC diagnostic push
  56. #pragma GCC diagnostic ignored "-Wsign-conversion"
  57. #endif
  58. namespace boost {
  59. namespace movelib {
  60. namespace pdqsort_detail {
  61. //A simple pair implementation to avoid including <utility>
  62. template<class T1, class T2>
  63. struct pair
  64. {
  65. pair()
  66. {}
  67. pair(const T1 &t1, const T2 &t2)
  68. : first(t1), second(t2)
  69. {}
  70. T1 first;
  71. T2 second;
  72. };
  73. enum {
  74. // Partitions below this size are sorted using insertion sort.
  75. insertion_sort_threshold = 24,
  76. // Partitions above this size use Tukey's ninther to select the pivot.
  77. ninther_threshold = 128,
  78. // When we detect an already sorted partition, attempt an insertion sort that allows this
  79. // amount of element moves before giving up.
  80. partial_insertion_sort_limit = 8,
  81. // Must be multiple of 8 due to loop unrolling, and < 256 to fit in unsigned char.
  82. block_size = 64,
  83. // Cacheline size, assumes power of two.
  84. cacheline_size = 64
  85. };
  86. // Returns floor(log2(n)), assumes n > 0.
  87. template<class Unsigned>
  88. Unsigned log2(Unsigned n) {
  89. Unsigned log = 0;
  90. while (n >>= 1) ++log;
  91. return log;
  92. }
  93. // Attempts to use insertion sort on [begin, end). Will return false if more than
  94. // partial_insertion_sort_limit elements were moved, and abort sorting. Otherwise it will
  95. // successfully sort and return true.
  96. template<class Iter, class Compare>
  97. inline bool partial_insertion_sort(Iter begin, Iter end, Compare comp) {
  98. typedef typename boost::movelib::iterator_traits<Iter>::value_type T;
  99. typedef typename boost::movelib:: iter_size<Iter>::type size_type;
  100. if (begin == end) return true;
  101. size_type limit = 0;
  102. for (Iter cur = begin + 1; cur != end; ++cur) {
  103. if (limit > partial_insertion_sort_limit) return false;
  104. Iter sift = cur;
  105. Iter sift_1 = cur - 1;
  106. // Compare first so we can avoid 2 moves for an element already positioned correctly.
  107. if (comp(*sift, *sift_1)) {
  108. T tmp = boost::move(*sift);
  109. do { *sift-- = boost::move(*sift_1); }
  110. while (sift != begin && comp(tmp, *--sift_1));
  111. *sift = boost::move(tmp);
  112. limit += size_type(cur - sift);
  113. }
  114. }
  115. return true;
  116. }
  117. template<class Iter, class Compare>
  118. inline void sort2(Iter a, Iter b, Compare comp) {
  119. if (comp(*b, *a)) boost::adl_move_iter_swap(a, b);
  120. }
  121. // Sorts the elements *a, *b and *c using comparison function comp.
  122. template<class Iter, class Compare>
  123. inline void sort3(Iter a, Iter b, Iter c, Compare comp) {
  124. sort2(a, b, comp);
  125. sort2(b, c, comp);
  126. sort2(a, b, comp);
  127. }
  128. // Partitions [begin, end) around pivot *begin using comparison function comp. Elements equal
  129. // to the pivot are put in the right-hand partition. Returns the position of the pivot after
  130. // partitioning and whether the passed sequence already was correctly partitioned. Assumes the
  131. // pivot is a median of at least 3 elements and that [begin, end) is at least
  132. // insertion_sort_threshold long.
  133. template<class Iter, class Compare>
  134. pdqsort_detail::pair<Iter, bool> partition_right(Iter begin, Iter end, Compare comp) {
  135. typedef typename boost::movelib::iterator_traits<Iter>::value_type T;
  136. // Move pivot into local for speed.
  137. T pivot(boost::move(*begin));
  138. Iter first = begin;
  139. Iter last = end;
  140. // Find the first element greater than or equal than the pivot (the median of 3 guarantees
  141. // this exists).
  142. while (comp(*++first, pivot));
  143. // Find the first element strictly smaller than the pivot. We have to guard this search if
  144. // there was no element before *first.
  145. if (first - 1 == begin) while (first < last && !comp(*--last, pivot));
  146. else while ( !comp(*--last, pivot));
  147. // If the first pair of elements that should be swapped to partition are the same element,
  148. // the passed in sequence already was correctly partitioned.
  149. bool already_partitioned = first >= last;
  150. // Keep swapping pairs of elements that are on the wrong side of the pivot. Previously
  151. // swapped pairs guard the searches, which is why the first iteration is special-cased
  152. // above.
  153. while (first < last) {
  154. boost::adl_move_iter_swap(first, last);
  155. while (comp(*++first, pivot));
  156. while (!comp(*--last, pivot));
  157. }
  158. // Put the pivot in the right place.
  159. Iter pivot_pos = first - 1;
  160. if(begin != pivot_pos) //Avoid potential self-move
  161. *begin = boost::move(*pivot_pos);
  162. *pivot_pos = boost::move(pivot);
  163. return pdqsort_detail::pair<Iter, bool>(pivot_pos, already_partitioned);
  164. }
  165. // Similar function to the one above, except elements equal to the pivot are put to the left of
  166. // the pivot and it doesn't check or return if the passed sequence already was partitioned.
  167. // Since this is rarely used (the many equal case), and in that case pdqsort already has O(n)
  168. // performance, no block quicksort is applied here for simplicity.
  169. template<class Iter, class Compare>
  170. inline Iter partition_left(Iter begin, Iter end, Compare comp) {
  171. typedef typename boost::movelib::iterator_traits<Iter>::value_type T;
  172. T pivot(boost::move(*begin));
  173. Iter first = begin;
  174. Iter last = end;
  175. while (comp(pivot, *--last));
  176. if (last + 1 == end) while (first < last && !comp(pivot, *++first));
  177. else while ( !comp(pivot, *++first));
  178. while (first < last) {
  179. boost::adl_move_iter_swap(first, last);
  180. while (comp(pivot, *--last));
  181. while (!comp(pivot, *++first));
  182. }
  183. Iter pivot_pos = last;
  184. *begin = boost::move(*pivot_pos);
  185. *pivot_pos = boost::move(pivot);
  186. return pivot_pos;
  187. }
  188. template<class Iter, class Compare>
  189. void pdqsort_loop( Iter begin, Iter end, Compare comp
  190. , typename boost::movelib:: iter_size<Iter>::type bad_allowed
  191. , bool leftmost = true)
  192. {
  193. typedef typename boost::movelib:: iter_size<Iter>::type size_type;
  194. // Use a while loop for tail recursion elimination.
  195. while (true) {
  196. size_type size = size_type(end - begin);
  197. // Insertion sort is faster for small arrays.
  198. if (size < insertion_sort_threshold) {
  199. insertion_sort(begin, end, comp);
  200. return;
  201. }
  202. // Choose pivot as median of 3 or pseudomedian of 9.
  203. size_type s2 = size / 2;
  204. if (size > ninther_threshold) {
  205. sort3(begin, begin + s2, end - 1, comp);
  206. sort3(begin + 1, begin + (s2 - 1), end - 2, comp);
  207. sort3(begin + 2, begin + (s2 + 1), end - 3, comp);
  208. sort3(begin + (s2 - 1), begin + s2, begin + (s2 + 1), comp);
  209. boost::adl_move_iter_swap(begin, begin + s2);
  210. } else sort3(begin + s2, begin, end - 1, comp);
  211. // If *(begin - 1) is the end of the right partition of a previous partition operation
  212. // there is no element in [begin, end) that is smaller than *(begin - 1). Then if our
  213. // pivot compares equal to *(begin - 1) we change strategy, putting equal elements in
  214. // the left partition, greater elements in the right partition. We do not have to
  215. // recurse on the left partition, since it's sorted (all equal).
  216. if (!leftmost && !comp(*(begin - 1), *begin)) {
  217. begin = partition_left(begin, end, comp) + 1;
  218. continue;
  219. }
  220. // Partition and get results.
  221. pdqsort_detail::pair<Iter, bool> part_result = partition_right(begin, end, comp);
  222. Iter pivot_pos = part_result.first;
  223. bool already_partitioned = part_result.second;
  224. // Check for a highly unbalanced partition.
  225. size_type l_size = size_type(pivot_pos - begin);
  226. size_type r_size = size_type(end - (pivot_pos + 1));
  227. bool highly_unbalanced = l_size < size / 8 || r_size < size / 8;
  228. // If we got a highly unbalanced partition we shuffle elements to break many patterns.
  229. if (highly_unbalanced) {
  230. // If we had too many bad partitions, switch to heapsort to guarantee O(n log n).
  231. if (--bad_allowed == 0) {
  232. boost::movelib::heap_sort(begin, end, comp);
  233. return;
  234. }
  235. if (l_size >= insertion_sort_threshold) {
  236. boost::adl_move_iter_swap(begin, begin + l_size / 4);
  237. boost::adl_move_iter_swap(pivot_pos - 1, pivot_pos - l_size / 4);
  238. if (l_size > ninther_threshold) {
  239. boost::adl_move_iter_swap(begin + 1, begin + (l_size / 4 + 1));
  240. boost::adl_move_iter_swap(begin + 2, begin + (l_size / 4 + 2));
  241. boost::adl_move_iter_swap(pivot_pos - 2, pivot_pos - (l_size / 4 + 1));
  242. boost::adl_move_iter_swap(pivot_pos - 3, pivot_pos - (l_size / 4 + 2));
  243. }
  244. }
  245. if (r_size >= insertion_sort_threshold) {
  246. boost::adl_move_iter_swap(pivot_pos + 1, pivot_pos + (1 + r_size / 4));
  247. boost::adl_move_iter_swap(end - 1, end - r_size / 4);
  248. if (r_size > ninther_threshold) {
  249. boost::adl_move_iter_swap(pivot_pos + 2, pivot_pos + (2 + r_size / 4));
  250. boost::adl_move_iter_swap(pivot_pos + 3, pivot_pos + (3 + r_size / 4));
  251. boost::adl_move_iter_swap(end - 2, end - (1 + r_size / 4));
  252. boost::adl_move_iter_swap(end - 3, end - (2 + r_size / 4));
  253. }
  254. }
  255. } else {
  256. // If we were decently balanced and we tried to sort an already partitioned
  257. // sequence try to use insertion sort.
  258. if (already_partitioned && partial_insertion_sort(begin, pivot_pos, comp)
  259. && partial_insertion_sort(pivot_pos + 1, end, comp)) return;
  260. }
  261. // Sort the left partition first using recursion and do tail recursion elimination for
  262. // the right-hand partition.
  263. pdqsort_loop<Iter, Compare>(begin, pivot_pos, comp, bad_allowed, leftmost);
  264. begin = pivot_pos + 1;
  265. leftmost = false;
  266. }
  267. }
  268. }
  269. template<class Iter, class Compare>
  270. void pdqsort(Iter begin, Iter end, Compare comp)
  271. {
  272. if (begin == end) return;
  273. typedef typename boost::movelib:: iter_size<Iter>::type size_type;
  274. pdqsort_detail::pdqsort_loop<Iter, Compare>(begin, end, comp, pdqsort_detail::log2(size_type(end - begin)));
  275. }
  276. } //namespace movelib {
  277. } //namespace boost {
  278. #if defined(BOOST_CLANG) || (defined(BOOST_GCC) && (BOOST_GCC >= 40600))
  279. #pragma GCC diagnostic pop
  280. #endif
  281. #include <boost/move/detail/config_end.hpp>
  282. #endif //BOOST_MOVE_ALGO_PDQSORT_HPP