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
38 #ifndef BOOST_MOVE_ALGO_PDQSORT_HPP
39 #define BOOST_MOVE_ALGO_PDQSORT_HPP
40
41 #ifndef BOOST_CONFIG_HPP
42 # include <boost/config.hpp>
43 #endif
44 #
45 #if defined(BOOST_HAS_PRAGMA_ONCE)
46 # pragma once
47 #endif
48
49 #include <boost/move/detail/config_begin.hpp>
50 #include <boost/move/detail/workaround.hpp>
51 #include <boost/move/utility_core.hpp>
52 #include <boost/move/algo/detail/insertion_sort.hpp>
53 #include <boost/move/algo/detail/heap_sort.hpp>
54 #include <boost/move/detail/iterator_traits.hpp>
55
56 #include <boost/move/adl_move_swap.hpp>
57 #include <cstddef>
58
59 namespace boost {
60 namespace movelib {
61
62 namespace pdqsort_detail {
63
64 //A simple pair implementation to avoid including <utility>
65 template<class T1, class T2>
66 struct pair
67 {
pairboost::movelib::pdqsort_detail::pair68 pair()
69 {}
70
pairboost::movelib::pdqsort_detail::pair71 pair(const T1 &t1, const T2 &t2)
72 : first(t1), second(t2)
73 {}
74
75 T1 first;
76 T2 second;
77 };
78
79 enum {
80 // Partitions below this size are sorted using insertion sort.
81 insertion_sort_threshold = 24,
82
83 // Partitions above this size use Tukey's ninther to select the pivot.
84 ninther_threshold = 128,
85
86 // When we detect an already sorted partition, attempt an insertion sort that allows this
87 // amount of element moves before giving up.
88 partial_insertion_sort_limit = 8,
89
90 // Must be multiple of 8 due to loop unrolling, and < 256 to fit in unsigned char.
91 block_size = 64,
92
93 // Cacheline size, assumes power of two.
94 cacheline_size = 64
95
96 };
97
98 // Returns floor(log2(n)), assumes n > 0.
99 template<class Unsigned>
log2(Unsigned n)100 Unsigned log2(Unsigned n) {
101 Unsigned log = 0;
102 while (n >>= 1) ++log;
103 return log;
104 }
105
106 // Attempts to use insertion sort on [begin, end). Will return false if more than
107 // partial_insertion_sort_limit elements were moved, and abort sorting. Otherwise it will
108 // successfully sort and return true.
109 template<class Iter, class Compare>
partial_insertion_sort(Iter begin,Iter end,Compare comp)110 inline bool partial_insertion_sort(Iter begin, Iter end, Compare comp) {
111 typedef typename boost::movelib::iterator_traits<Iter>::value_type T;
112 typedef typename boost::movelib::iterator_traits<Iter>::size_type size_type;
113 if (begin == end) return true;
114
115 size_type limit = 0;
116 for (Iter cur = begin + 1; cur != end; ++cur) {
117 if (limit > partial_insertion_sort_limit) return false;
118
119 Iter sift = cur;
120 Iter sift_1 = cur - 1;
121
122 // Compare first so we can avoid 2 moves for an element already positioned correctly.
123 if (comp(*sift, *sift_1)) {
124 T tmp = boost::move(*sift);
125
126 do { *sift-- = boost::move(*sift_1); }
127 while (sift != begin && comp(tmp, *--sift_1));
128
129 *sift = boost::move(tmp);
130 limit += size_type(cur - sift);
131 }
132 }
133
134 return true;
135 }
136
137 template<class Iter, class Compare>
sort2(Iter a,Iter b,Compare comp)138 inline void sort2(Iter a, Iter b, Compare comp) {
139 if (comp(*b, *a)) boost::adl_move_iter_swap(a, b);
140 }
141
142 // Sorts the elements *a, *b and *c using comparison function comp.
143 template<class Iter, class Compare>
sort3(Iter a,Iter b,Iter c,Compare comp)144 inline void sort3(Iter a, Iter b, Iter c, Compare comp) {
145 sort2(a, b, comp);
146 sort2(b, c, comp);
147 sort2(a, b, comp);
148 }
149
150 // Partitions [begin, end) around pivot *begin using comparison function comp. Elements equal
151 // to the pivot are put in the right-hand partition. Returns the position of the pivot after
152 // partitioning and whether the passed sequence already was correctly partitioned. Assumes the
153 // pivot is a median of at least 3 elements and that [begin, end) is at least
154 // insertion_sort_threshold long.
155 template<class Iter, class Compare>
partition_right(Iter begin,Iter end,Compare comp)156 pdqsort_detail::pair<Iter, bool> partition_right(Iter begin, Iter end, Compare comp) {
157 typedef typename boost::movelib::iterator_traits<Iter>::value_type T;
158
159 // Move pivot into local for speed.
160 T pivot(boost::move(*begin));
161
162 Iter first = begin;
163 Iter last = end;
164
165 // Find the first element greater than or equal than the pivot (the median of 3 guarantees
166 // this exists).
167 while (comp(*++first, pivot));
168
169 // Find the first element strictly smaller than the pivot. We have to guard this search if
170 // there was no element before *first.
171 if (first - 1 == begin) while (first < last && !comp(*--last, pivot));
172 else while ( !comp(*--last, pivot));
173
174 // If the first pair of elements that should be swapped to partition are the same element,
175 // the passed in sequence already was correctly partitioned.
176 bool already_partitioned = first >= last;
177
178 // Keep swapping pairs of elements that are on the wrong side of the pivot. Previously
179 // swapped pairs guard the searches, which is why the first iteration is special-cased
180 // above.
181 while (first < last) {
182 boost::adl_move_iter_swap(first, last);
183 while (comp(*++first, pivot));
184 while (!comp(*--last, pivot));
185 }
186
187 // Put the pivot in the right place.
188 Iter pivot_pos = first - 1;
189 *begin = boost::move(*pivot_pos);
190 *pivot_pos = boost::move(pivot);
191
192 return pdqsort_detail::pair<Iter, bool>(pivot_pos, already_partitioned);
193 }
194
195 // Similar function to the one above, except elements equal to the pivot are put to the left of
196 // the pivot and it doesn't check or return if the passed sequence already was partitioned.
197 // Since this is rarely used (the many equal case), and in that case pdqsort already has O(n)
198 // performance, no block quicksort is applied here for simplicity.
199 template<class Iter, class Compare>
partition_left(Iter begin,Iter end,Compare comp)200 inline Iter partition_left(Iter begin, Iter end, Compare comp) {
201 typedef typename boost::movelib::iterator_traits<Iter>::value_type T;
202
203 T pivot(boost::move(*begin));
204 Iter first = begin;
205 Iter last = end;
206
207 while (comp(pivot, *--last));
208
209 if (last + 1 == end) while (first < last && !comp(pivot, *++first));
210 else while ( !comp(pivot, *++first));
211
212 while (first < last) {
213 boost::adl_move_iter_swap(first, last);
214 while (comp(pivot, *--last));
215 while (!comp(pivot, *++first));
216 }
217
218 Iter pivot_pos = last;
219 *begin = boost::move(*pivot_pos);
220 *pivot_pos = boost::move(pivot);
221
222 return pivot_pos;
223 }
224
225
226 template<class Iter, class Compare>
pdqsort_loop(Iter begin,Iter end,Compare comp,typename boost::movelib::iterator_traits<Iter>::size_type bad_allowed,bool leftmost=true)227 void pdqsort_loop( Iter begin, Iter end, Compare comp
228 , typename boost::movelib::iterator_traits<Iter>::size_type bad_allowed
229 , bool leftmost = true)
230 {
231 typedef typename boost::movelib::iterator_traits<Iter>::size_type size_type;
232
233 // Use a while loop for tail recursion elimination.
234 while (true) {
235 size_type size = size_type(end - begin);
236
237 // Insertion sort is faster for small arrays.
238 if (size < insertion_sort_threshold) {
239 insertion_sort(begin, end, comp);
240 return;
241 }
242
243 // Choose pivot as median of 3 or pseudomedian of 9.
244 size_type s2 = size / 2;
245 if (size > ninther_threshold) {
246 sort3(begin, begin + s2, end - 1, comp);
247 sort3(begin + 1, begin + (s2 - 1), end - 2, comp);
248 sort3(begin + 2, begin + (s2 + 1), end - 3, comp);
249 sort3(begin + (s2 - 1), begin + s2, begin + (s2 + 1), comp);
250 boost::adl_move_iter_swap(begin, begin + s2);
251 } else sort3(begin + s2, begin, end - 1, comp);
252
253 // If *(begin - 1) is the end of the right partition of a previous partition operation
254 // there is no element in [begin, end) that is smaller than *(begin - 1). Then if our
255 // pivot compares equal to *(begin - 1) we change strategy, putting equal elements in
256 // the left partition, greater elements in the right partition. We do not have to
257 // recurse on the left partition, since it's sorted (all equal).
258 if (!leftmost && !comp(*(begin - 1), *begin)) {
259 begin = partition_left(begin, end, comp) + 1;
260 continue;
261 }
262
263 // Partition and get results.
264 pdqsort_detail::pair<Iter, bool> part_result = partition_right(begin, end, comp);
265 Iter pivot_pos = part_result.first;
266 bool already_partitioned = part_result.second;
267
268 // Check for a highly unbalanced partition.
269 size_type l_size = size_type(pivot_pos - begin);
270 size_type r_size = size_type(end - (pivot_pos + 1));
271 bool highly_unbalanced = l_size < size / 8 || r_size < size / 8;
272
273 // If we got a highly unbalanced partition we shuffle elements to break many patterns.
274 if (highly_unbalanced) {
275 // If we had too many bad partitions, switch to heapsort to guarantee O(n log n).
276 if (--bad_allowed == 0) {
277 boost::movelib::heap_sort(begin, end, comp);
278 return;
279 }
280
281 if (l_size >= insertion_sort_threshold) {
282 boost::adl_move_iter_swap(begin, begin + l_size / 4);
283 boost::adl_move_iter_swap(pivot_pos - 1, pivot_pos - l_size / 4);
284
285 if (l_size > ninther_threshold) {
286 boost::adl_move_iter_swap(begin + 1, begin + (l_size / 4 + 1));
287 boost::adl_move_iter_swap(begin + 2, begin + (l_size / 4 + 2));
288 boost::adl_move_iter_swap(pivot_pos - 2, pivot_pos - (l_size / 4 + 1));
289 boost::adl_move_iter_swap(pivot_pos - 3, pivot_pos - (l_size / 4 + 2));
290 }
291 }
292
293 if (r_size >= insertion_sort_threshold) {
294 boost::adl_move_iter_swap(pivot_pos + 1, pivot_pos + (1 + r_size / 4));
295 boost::adl_move_iter_swap(end - 1, end - r_size / 4);
296
297 if (r_size > ninther_threshold) {
298 boost::adl_move_iter_swap(pivot_pos + 2, pivot_pos + (2 + r_size / 4));
299 boost::adl_move_iter_swap(pivot_pos + 3, pivot_pos + (3 + r_size / 4));
300 boost::adl_move_iter_swap(end - 2, end - (1 + r_size / 4));
301 boost::adl_move_iter_swap(end - 3, end - (2 + r_size / 4));
302 }
303 }
304 } else {
305 // If we were decently balanced and we tried to sort an already partitioned
306 // sequence try to use insertion sort.
307 if (already_partitioned && partial_insertion_sort(begin, pivot_pos, comp)
308 && partial_insertion_sort(pivot_pos + 1, end, comp)) return;
309 }
310
311 // Sort the left partition first using recursion and do tail recursion elimination for
312 // the right-hand partition.
313 pdqsort_loop<Iter, Compare>(begin, pivot_pos, comp, bad_allowed, leftmost);
314 begin = pivot_pos + 1;
315 leftmost = false;
316 }
317 }
318 }
319
320
321 template<class Iter, class Compare>
pdqsort(Iter begin,Iter end,Compare comp)322 void pdqsort(Iter begin, Iter end, Compare comp)
323 {
324 if (begin == end) return;
325 typedef typename boost::movelib::iterator_traits<Iter>::size_type size_type;
326 pdqsort_detail::pdqsort_loop<Iter, Compare>(begin, end, comp, pdqsort_detail::log2(size_type(end - begin)));
327 }
328
329 } //namespace movelib {
330 } //namespace boost {
331
332 #include <boost/move/detail/config_end.hpp>
333
334 #endif //BOOST_MOVE_ALGO_PDQSORT_HPP
335