• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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