1 // Details for templated Spreadsort-based integer_sort. 2 3 // Copyright Steven J. Ross 2001 - 2014. 4 // Distributed under the Boost Software License, Version 1.0. 5 // (See accompanying file LICENSE_1_0.txt or copy at 6 // http://www.boost.org/LICENSE_1_0.txt) 7 8 // See http://www.boost.org/libs/sort for library home page. 9 10 /* 11 Some improvements suggested by: 12 Phil Endecott and Frank Gennari 13 */ 14 15 #ifndef BOOST_SORT_SPREADSORT_DETAIL_INTEGER_SORT_HPP 16 #define BOOST_SORT_SPREADSORT_DETAIL_INTEGER_SORT_HPP 17 #include <algorithm> 18 #include <vector> 19 #include <limits> 20 #include <functional> 21 #include <boost/static_assert.hpp> 22 #include <boost/utility/enable_if.hpp> 23 #include <boost/sort/spreadsort/detail/constants.hpp> 24 #include <boost/sort/spreadsort/detail/spreadsort_common.hpp> 25 #include <boost/cstdint.hpp> 26 27 namespace boost { 28 namespace sort { 29 namespace spreadsort { 30 namespace detail { 31 // Return true if the list is sorted. Otherwise, find the minimum and 32 // maximum using <. 33 template <class RandomAccessIter> 34 inline bool is_sorted_or_find_extremes(RandomAccessIter current,RandomAccessIter last,RandomAccessIter & max,RandomAccessIter & min)35 is_sorted_or_find_extremes(RandomAccessIter current, RandomAccessIter last, 36 RandomAccessIter & max, RandomAccessIter & min) 37 { 38 min = max = current; 39 //This assumes we have more than 1 element based on prior checks. 40 while (!(*(current + 1) < *current)) { 41 //If everything is in sorted order, return 42 if (++current == last - 1) 43 return true; 44 } 45 46 //The maximum is the last sorted element 47 max = current; 48 //Start from the first unsorted element 49 while (++current < last) { 50 if (*max < *current) 51 max = current; 52 else if (*current < *min) 53 min = current; 54 } 55 return false; 56 } 57 58 // Return true if the list is sorted. Otherwise, find the minimum and 59 // maximum. 60 // Use a user-defined comparison operator 61 template <class RandomAccessIter, class Compare> 62 inline bool is_sorted_or_find_extremes(RandomAccessIter current,RandomAccessIter last,RandomAccessIter & max,RandomAccessIter & min,Compare comp)63 is_sorted_or_find_extremes(RandomAccessIter current, RandomAccessIter last, 64 RandomAccessIter & max, RandomAccessIter & min, Compare comp) 65 { 66 min = max = current; 67 while (!comp(*(current + 1), *current)) { 68 //If everything is in sorted order, return 69 if (++current == last - 1) 70 return true; 71 } 72 73 //The maximum is the last sorted element 74 max = current; 75 while (++current < last) { 76 if (comp(*max, *current)) 77 max = current; 78 else if (comp(*current, *min)) 79 min = current; 80 } 81 return false; 82 } 83 84 //Gets a non-negative right bit shift to operate as a logarithmic divisor 85 template<unsigned log_mean_bin_size> 86 inline int get_log_divisor(size_t count,int log_range)87 get_log_divisor(size_t count, int log_range) 88 { 89 int log_divisor; 90 //If we can finish in one iteration without exceeding either 91 //(2 to the max_finishing_splits) or n bins, do so 92 if ((log_divisor = log_range - rough_log_2_size(count)) <= 0 && 93 log_range <= max_finishing_splits) 94 log_divisor = 0; 95 else { 96 //otherwise divide the data into an optimized number of pieces 97 log_divisor += log_mean_bin_size; 98 //Cannot exceed max_splits or cache misses slow down bin lookups 99 if ((log_range - log_divisor) > max_splits) 100 log_divisor = log_range - max_splits; 101 } 102 return log_divisor; 103 } 104 105 //Implementation for recursive integer sorting 106 template <class RandomAccessIter, class Div_type, class Size_type> 107 inline void spreadsort_rec(RandomAccessIter first,RandomAccessIter last,std::vector<RandomAccessIter> & bin_cache,unsigned cache_offset,size_t * bin_sizes)108 spreadsort_rec(RandomAccessIter first, RandomAccessIter last, 109 std::vector<RandomAccessIter> &bin_cache, unsigned cache_offset 110 , size_t *bin_sizes) 111 { 112 //This step is roughly 10% of runtime, but it helps avoid worst-case 113 //behavior and improve behavior with real data 114 //If you know the maximum and minimum ahead of time, you can pass those 115 //values in and skip this step for the first iteration 116 RandomAccessIter max, min; 117 if (is_sorted_or_find_extremes(first, last, max, min)) 118 return; 119 RandomAccessIter * target_bin; 120 unsigned log_divisor = get_log_divisor<int_log_mean_bin_size>( 121 last - first, rough_log_2_size(Size_type((*max >> 0) - (*min >> 0)))); 122 Div_type div_min = *min >> log_divisor; 123 Div_type div_max = *max >> log_divisor; 124 unsigned bin_count = unsigned(div_max - div_min) + 1; 125 unsigned cache_end; 126 RandomAccessIter * bins = 127 size_bins(bin_sizes, bin_cache, cache_offset, cache_end, bin_count); 128 129 //Calculating the size of each bin; this takes roughly 10% of runtime 130 for (RandomAccessIter current = first; current != last;) 131 bin_sizes[size_t((*(current++) >> log_divisor) - div_min)]++; 132 //Assign the bin positions 133 bins[0] = first; 134 for (unsigned u = 0; u < bin_count - 1; u++) 135 bins[u + 1] = bins[u] + bin_sizes[u]; 136 137 RandomAccessIter nextbinstart = first; 138 //Swap into place 139 //This dominates runtime, mostly in the swap and bin lookups 140 for (unsigned u = 0; u < bin_count - 1; ++u) { 141 RandomAccessIter * local_bin = bins + u; 142 nextbinstart += bin_sizes[u]; 143 //Iterating over each element in this bin 144 for (RandomAccessIter current = *local_bin; current < nextbinstart; 145 ++current) { 146 //Swapping elements in current into place until the correct 147 //element has been swapped in 148 for (target_bin = (bins + ((*current >> log_divisor) - div_min)); 149 target_bin != local_bin; 150 target_bin = bins + ((*current >> log_divisor) - div_min)) { 151 //3-way swap; this is about 1% faster than a 2-way swap 152 //The main advantage is less copies are involved per item 153 //put in the correct place 154 typename std::iterator_traits<RandomAccessIter>::value_type tmp; 155 RandomAccessIter b = (*target_bin)++; 156 RandomAccessIter * b_bin = bins + ((*b >> log_divisor) - div_min); 157 if (b_bin != local_bin) { 158 RandomAccessIter c = (*b_bin)++; 159 tmp = *c; 160 *c = *b; 161 } 162 else 163 tmp = *b; 164 *b = *current; 165 *current = tmp; 166 } 167 } 168 *local_bin = nextbinstart; 169 } 170 bins[bin_count - 1] = last; 171 172 //If we've bucketsorted, the array is sorted and we should skip recursion 173 if (!log_divisor) 174 return; 175 //log_divisor is the remaining range; calculating the comparison threshold 176 size_t max_count = 177 get_min_count<int_log_mean_bin_size, int_log_min_split_count, 178 int_log_finishing_count>(log_divisor); 179 180 //Recursing 181 RandomAccessIter lastPos = first; 182 for (unsigned u = cache_offset; u < cache_end; lastPos = bin_cache[u], 183 ++u) { 184 Size_type count = bin_cache[u] - lastPos; 185 //don't sort unless there are at least two items to Compare 186 if (count < 2) 187 continue; 188 //using boost::sort::pdqsort if its worst-case is better 189 if (count < max_count) 190 boost::sort::pdqsort(lastPos, bin_cache[u]); 191 else 192 spreadsort_rec<RandomAccessIter, Div_type, Size_type>(lastPos, 193 bin_cache[u], 194 bin_cache, 195 cache_end, 196 bin_sizes); 197 } 198 } 199 200 //Generic bitshift-based 3-way swapping code 201 template <class RandomAccessIter, class Div_type, class Right_shift> inner_swap_loop(RandomAccessIter * bins,const RandomAccessIter & next_bin_start,unsigned ii,Right_shift & rshift,const unsigned log_divisor,const Div_type div_min)202 inline void inner_swap_loop(RandomAccessIter * bins, 203 const RandomAccessIter & next_bin_start, unsigned ii, Right_shift &rshift 204 , const unsigned log_divisor, const Div_type div_min) 205 { 206 RandomAccessIter * local_bin = bins + ii; 207 for (RandomAccessIter current = *local_bin; current < next_bin_start; 208 ++current) { 209 for (RandomAccessIter * target_bin = 210 (bins + (rshift(*current, log_divisor) - div_min)); 211 target_bin != local_bin; 212 target_bin = bins + (rshift(*current, log_divisor) - div_min)) { 213 typename std::iterator_traits<RandomAccessIter>::value_type tmp; 214 RandomAccessIter b = (*target_bin)++; 215 RandomAccessIter * b_bin = 216 bins + (rshift(*b, log_divisor) - div_min); 217 //Three-way swap; if the item to be swapped doesn't belong 218 //in the current bin, swap it to where it belongs 219 if (b_bin != local_bin) { 220 RandomAccessIter c = (*b_bin)++; 221 tmp = *c; 222 *c = *b; 223 } 224 //Note: we could increment current once the swap is done in this case 225 //but that seems to impair performance 226 else 227 tmp = *b; 228 *b = *current; 229 *current = tmp; 230 } 231 } 232 *local_bin = next_bin_start; 233 } 234 235 //Standard swapping wrapper for ascending values 236 template <class RandomAccessIter, class Div_type, class Right_shift> swap_loop(RandomAccessIter * bins,RandomAccessIter & next_bin_start,unsigned ii,Right_shift & rshift,const size_t * bin_sizes,const unsigned log_divisor,const Div_type div_min)237 inline void swap_loop(RandomAccessIter * bins, 238 RandomAccessIter & next_bin_start, unsigned ii, Right_shift &rshift 239 , const size_t *bin_sizes 240 , const unsigned log_divisor, const Div_type div_min) 241 { 242 next_bin_start += bin_sizes[ii]; 243 inner_swap_loop<RandomAccessIter, Div_type, Right_shift>(bins, 244 next_bin_start, ii, rshift, log_divisor, div_min); 245 } 246 247 //Functor implementation for recursive sorting 248 template <class RandomAccessIter, class Div_type, class Right_shift, 249 class Compare, class Size_type, unsigned log_mean_bin_size, 250 unsigned log_min_split_count, unsigned log_finishing_count> 251 inline void spreadsort_rec(RandomAccessIter first,RandomAccessIter last,std::vector<RandomAccessIter> & bin_cache,unsigned cache_offset,size_t * bin_sizes,Right_shift rshift,Compare comp)252 spreadsort_rec(RandomAccessIter first, RandomAccessIter last, 253 std::vector<RandomAccessIter> &bin_cache, unsigned cache_offset 254 , size_t *bin_sizes, Right_shift rshift, Compare comp) 255 { 256 RandomAccessIter max, min; 257 if (is_sorted_or_find_extremes(first, last, max, min, comp)) 258 return; 259 unsigned log_divisor = get_log_divisor<log_mean_bin_size>(last - first, 260 rough_log_2_size(Size_type(rshift(*max, 0) - rshift(*min, 0)))); 261 Div_type div_min = rshift(*min, log_divisor); 262 Div_type div_max = rshift(*max, log_divisor); 263 unsigned bin_count = unsigned(div_max - div_min) + 1; 264 unsigned cache_end; 265 RandomAccessIter * bins = size_bins(bin_sizes, bin_cache, cache_offset, 266 cache_end, bin_count); 267 268 //Calculating the size of each bin 269 for (RandomAccessIter current = first; current != last;) 270 bin_sizes[unsigned(rshift(*(current++), log_divisor) - div_min)]++; 271 bins[0] = first; 272 for (unsigned u = 0; u < bin_count - 1; u++) 273 bins[u + 1] = bins[u] + bin_sizes[u]; 274 275 //Swap into place 276 RandomAccessIter next_bin_start = first; 277 for (unsigned u = 0; u < bin_count - 1; ++u) 278 swap_loop<RandomAccessIter, Div_type, Right_shift>(bins, next_bin_start, 279 u, rshift, bin_sizes, log_divisor, div_min); 280 bins[bin_count - 1] = last; 281 282 //If we've bucketsorted, the array is sorted 283 if (!log_divisor) 284 return; 285 286 //Recursing 287 size_t max_count = get_min_count<log_mean_bin_size, log_min_split_count, 288 log_finishing_count>(log_divisor); 289 RandomAccessIter lastPos = first; 290 for (unsigned u = cache_offset; u < cache_end; lastPos = bin_cache[u], 291 ++u) { 292 size_t count = bin_cache[u] - lastPos; 293 if (count < 2) 294 continue; 295 if (count < max_count) 296 boost::sort::pdqsort(lastPos, bin_cache[u], comp); 297 else 298 spreadsort_rec<RandomAccessIter, Div_type, Right_shift, Compare, 299 Size_type, log_mean_bin_size, log_min_split_count, log_finishing_count> 300 (lastPos, bin_cache[u], bin_cache, cache_end, bin_sizes, rshift, comp); 301 } 302 } 303 304 //Functor implementation for recursive sorting with only Shift overridden 305 template <class RandomAccessIter, class Div_type, class Right_shift, 306 class Size_type, unsigned log_mean_bin_size, 307 unsigned log_min_split_count, unsigned log_finishing_count> 308 inline void spreadsort_rec(RandomAccessIter first,RandomAccessIter last,std::vector<RandomAccessIter> & bin_cache,unsigned cache_offset,size_t * bin_sizes,Right_shift rshift)309 spreadsort_rec(RandomAccessIter first, RandomAccessIter last, 310 std::vector<RandomAccessIter> &bin_cache, unsigned cache_offset 311 , size_t *bin_sizes, Right_shift rshift) 312 { 313 RandomAccessIter max, min; 314 if (is_sorted_or_find_extremes(first, last, max, min)) 315 return; 316 unsigned log_divisor = get_log_divisor<log_mean_bin_size>(last - first, 317 rough_log_2_size(Size_type(rshift(*max, 0) - rshift(*min, 0)))); 318 Div_type div_min = rshift(*min, log_divisor); 319 Div_type div_max = rshift(*max, log_divisor); 320 unsigned bin_count = unsigned(div_max - div_min) + 1; 321 unsigned cache_end; 322 RandomAccessIter * bins = size_bins(bin_sizes, bin_cache, cache_offset, 323 cache_end, bin_count); 324 325 //Calculating the size of each bin 326 for (RandomAccessIter current = first; current != last;) 327 bin_sizes[unsigned(rshift(*(current++), log_divisor) - div_min)]++; 328 bins[0] = first; 329 for (unsigned u = 0; u < bin_count - 1; u++) 330 bins[u + 1] = bins[u] + bin_sizes[u]; 331 332 //Swap into place 333 RandomAccessIter nextbinstart = first; 334 for (unsigned ii = 0; ii < bin_count - 1; ++ii) 335 swap_loop<RandomAccessIter, Div_type, Right_shift>(bins, nextbinstart, 336 ii, rshift, bin_sizes, log_divisor, div_min); 337 bins[bin_count - 1] = last; 338 339 //If we've bucketsorted, the array is sorted 340 if (!log_divisor) 341 return; 342 343 //Recursing 344 size_t max_count = get_min_count<log_mean_bin_size, log_min_split_count, 345 log_finishing_count>(log_divisor); 346 RandomAccessIter lastPos = first; 347 for (unsigned u = cache_offset; u < cache_end; lastPos = bin_cache[u], 348 ++u) { 349 size_t count = bin_cache[u] - lastPos; 350 if (count < 2) 351 continue; 352 if (count < max_count) 353 boost::sort::pdqsort(lastPos, bin_cache[u]); 354 else 355 spreadsort_rec<RandomAccessIter, Div_type, Right_shift, Size_type, 356 log_mean_bin_size, log_min_split_count, log_finishing_count>(lastPos, 357 bin_cache[u], bin_cache, cache_end, bin_sizes, rshift); 358 } 359 } 360 361 //Holds the bin vector and makes the initial recursive call 362 template <class RandomAccessIter, class Div_type> 363 //Only use spreadsort if the integer can fit in a size_t 364 inline typename boost::enable_if_c< sizeof(Div_type) <= sizeof(size_t), 365 void >::type integer_sort(RandomAccessIter first,RandomAccessIter last,Div_type)366 integer_sort(RandomAccessIter first, RandomAccessIter last, Div_type) 367 { 368 size_t bin_sizes[1 << max_finishing_splits]; 369 std::vector<RandomAccessIter> bin_cache; 370 spreadsort_rec<RandomAccessIter, Div_type, size_t>(first, last, 371 bin_cache, 0, bin_sizes); 372 } 373 374 //Holds the bin vector and makes the initial recursive call 375 template <class RandomAccessIter, class Div_type> 376 //Only use spreadsort if the integer can fit in a uintmax_t 377 inline typename boost::enable_if_c< (sizeof(Div_type) > sizeof(size_t)) 378 && sizeof(Div_type) <= sizeof(boost::uintmax_t), void >::type integer_sort(RandomAccessIter first,RandomAccessIter last,Div_type)379 integer_sort(RandomAccessIter first, RandomAccessIter last, Div_type) 380 { 381 size_t bin_sizes[1 << max_finishing_splits]; 382 std::vector<RandomAccessIter> bin_cache; 383 spreadsort_rec<RandomAccessIter, Div_type, boost::uintmax_t>(first, 384 last, bin_cache, 0, bin_sizes); 385 } 386 387 template <class RandomAccessIter, class Div_type> 388 inline typename boost::disable_if_c< sizeof(Div_type) <= sizeof(size_t) 389 || sizeof(Div_type) <= sizeof(boost::uintmax_t), void >::type 390 //defaulting to boost::sort::pdqsort when integer_sort won't work integer_sort(RandomAccessIter first,RandomAccessIter last,Div_type)391 integer_sort(RandomAccessIter first, RandomAccessIter last, Div_type) 392 { 393 //Warning that we're using boost::sort::pdqsort, even though integer_sort was called 394 BOOST_STATIC_ASSERT( sizeof(Div_type) <= sizeof(size_t) ); 395 boost::sort::pdqsort(first, last); 396 } 397 398 399 //Same for the full functor version 400 template <class RandomAccessIter, class Div_type, class Right_shift, 401 class Compare> 402 //Only use spreadsort if the integer can fit in a size_t 403 inline typename boost::enable_if_c< sizeof(Div_type) <= sizeof(size_t), 404 void >::type integer_sort(RandomAccessIter first,RandomAccessIter last,Div_type,Right_shift shift,Compare comp)405 integer_sort(RandomAccessIter first, RandomAccessIter last, Div_type, 406 Right_shift shift, Compare comp) 407 { 408 size_t bin_sizes[1 << max_finishing_splits]; 409 std::vector<RandomAccessIter> bin_cache; 410 spreadsort_rec<RandomAccessIter, Div_type, Right_shift, Compare, 411 size_t, int_log_mean_bin_size, int_log_min_split_count, 412 int_log_finishing_count> 413 (first, last, bin_cache, 0, bin_sizes, shift, comp); 414 } 415 416 template <class RandomAccessIter, class Div_type, class Right_shift, 417 class Compare> 418 //Only use spreadsort if the integer can fit in a uintmax_t 419 inline typename boost::enable_if_c< (sizeof(Div_type) > sizeof(size_t)) 420 && sizeof(Div_type) <= sizeof(boost::uintmax_t), void >::type integer_sort(RandomAccessIter first,RandomAccessIter last,Div_type,Right_shift shift,Compare comp)421 integer_sort(RandomAccessIter first, RandomAccessIter last, Div_type, 422 Right_shift shift, Compare comp) 423 { 424 size_t bin_sizes[1 << max_finishing_splits]; 425 std::vector<RandomAccessIter> bin_cache; 426 spreadsort_rec<RandomAccessIter, Div_type, Right_shift, Compare, 427 boost::uintmax_t, int_log_mean_bin_size, 428 int_log_min_split_count, int_log_finishing_count> 429 (first, last, bin_cache, 0, bin_sizes, shift, comp); 430 } 431 432 template <class RandomAccessIter, class Div_type, class Right_shift, 433 class Compare> 434 inline typename boost::disable_if_c< sizeof(Div_type) <= sizeof(size_t) 435 || sizeof(Div_type) <= sizeof(boost::uintmax_t), void >::type 436 //defaulting to boost::sort::pdqsort when integer_sort won't work integer_sort(RandomAccessIter first,RandomAccessIter last,Div_type,Right_shift shift,Compare comp)437 integer_sort(RandomAccessIter first, RandomAccessIter last, Div_type, 438 Right_shift shift, Compare comp) 439 { 440 //Warning that we're using boost::sort::pdqsort, even though integer_sort was called 441 BOOST_STATIC_ASSERT( sizeof(Div_type) <= sizeof(size_t) ); 442 boost::sort::pdqsort(first, last, comp); 443 } 444 445 446 //Same for the right shift version 447 template <class RandomAccessIter, class Div_type, class Right_shift> 448 //Only use spreadsort if the integer can fit in a size_t 449 inline typename boost::enable_if_c< sizeof(Div_type) <= sizeof(size_t), 450 void >::type integer_sort(RandomAccessIter first,RandomAccessIter last,Div_type,Right_shift shift)451 integer_sort(RandomAccessIter first, RandomAccessIter last, Div_type, 452 Right_shift shift) 453 { 454 size_t bin_sizes[1 << max_finishing_splits]; 455 std::vector<RandomAccessIter> bin_cache; 456 spreadsort_rec<RandomAccessIter, Div_type, Right_shift, size_t, 457 int_log_mean_bin_size, int_log_min_split_count, 458 int_log_finishing_count> 459 (first, last, bin_cache, 0, bin_sizes, shift); 460 } 461 462 template <class RandomAccessIter, class Div_type, class Right_shift> 463 //Only use spreadsort if the integer can fit in a uintmax_t 464 inline typename boost::enable_if_c< (sizeof(Div_type) > sizeof(size_t)) 465 && sizeof(Div_type) <= sizeof(boost::uintmax_t), void >::type integer_sort(RandomAccessIter first,RandomAccessIter last,Div_type,Right_shift shift)466 integer_sort(RandomAccessIter first, RandomAccessIter last, Div_type, 467 Right_shift shift) 468 { 469 size_t bin_sizes[1 << max_finishing_splits]; 470 std::vector<RandomAccessIter> bin_cache; 471 spreadsort_rec<RandomAccessIter, Div_type, Right_shift, 472 boost::uintmax_t, int_log_mean_bin_size, 473 int_log_min_split_count, int_log_finishing_count> 474 (first, last, bin_cache, 0, bin_sizes, shift); 475 } 476 477 template <class RandomAccessIter, class Div_type, class Right_shift> 478 inline typename boost::disable_if_c< sizeof(Div_type) <= sizeof(size_t) 479 || sizeof(Div_type) <= sizeof(boost::uintmax_t), void >::type 480 //defaulting to boost::sort::pdqsort when integer_sort won't work integer_sort(RandomAccessIter first,RandomAccessIter last,Div_type,Right_shift shift)481 integer_sort(RandomAccessIter first, RandomAccessIter last, Div_type, 482 Right_shift shift) 483 { 484 //Warning that we're using boost::sort::pdqsort, even though integer_sort was called 485 BOOST_STATIC_ASSERT( sizeof(Div_type) <= sizeof(size_t) ); 486 boost::sort::pdqsort(first, last); 487 } 488 } 489 } 490 } 491 } 492 493 #endif 494