pax_global_header00006660000000000000000000000064132661570400014516gustar00rootroot0000000000000052 comment=08879029ab8dcb80a70142acb709e3df02de5d37 pdqsort-0.0.0+git20180419/000077500000000000000000000000001326615704000146175ustar00rootroot00000000000000pdqsort-0.0.0+git20180419/bench/000077500000000000000000000000001326615704000156765ustar00rootroot00000000000000pdqsort-0.0.0+git20180419/bench/bars.py000066400000000000000000000056661326615704000172140ustar00rootroot00000000000000import math import os import sys import numpy from matplotlib import pyplot as plt cpu_info = sys.argv[1] distribution_names = { "shuffled_16_values_int": "Shuffled (16 values)", "shuffled_int": "Shuffled", "all_equal_int": "All equal", "ascending_int": "Ascending", "descending_int": "Descending", "pipe_organ_int": "Pipe organ", "push_front_int": "Push front", "push_middle_int": "Push middle" } sort_order = ["pdqsort", "std::sort", "std::stable_sort", "timsort", "std::sort_heap"] for filename in os.listdir("profiles"): data = {} for line in open(os.path.join("profiles", filename)): size, distribution, algo, *results = line.split() size = int(size) distribution = distribution_names[distribution] results = [int(result) for result in results] if not size in data: data[size] = {} if not distribution in data[size]: data[size][distribution] = {} data[size][distribution][algo] = results for size in data: distributions = ("Shuffled", "Shuffled (16 values)", "All equal", "Ascending", "Descending", "Pipe organ", "Push front", "Push middle") algos = tuple(data[size]["Shuffled"].keys()) algos = tuple(sorted(algos, key=lambda a: sort_order.index(a) if a in sort_order else 1000)) groupnames = distributions groupsize = len(algos) groups = [[data[size][distribution][algo] for algo in algos] for distribution in distributions] barwidth = 0.6 spacing = 1 groupwidth = groupsize * barwidth + spacing colors = ["#1f77b4", "#aec7e8", "#ff7f0e", "#ffbb78", "#800080"] for i, algo in enumerate(algos): heights = [numpy.median(data[size][distribution][algo]) for distribution in distributions] errors = [numpy.std(data[size][distribution][algo]) for distribution in distributions] plt.barh([barwidth*i + groupwidth*n for n in range(len(distributions))], heights, 0.6, color = colors[i], label = algo) # Set axes limits and labels. plt.yticks([barwidth * groupsize/2 + groupwidth*n for n in range(len(groupnames))], groupnames) plt.xlabel("Cycles per element ({})".format(cpu_info)) # Turn off ticks for y-axis. plt.tick_params( axis="y", which="both", left="off", right="off", labelleft="on" ) ax = plt.gca() ax.invert_yaxis() ax.relim() ax.autoscale_view() plt.ylim(plt.ylim()[0]+1, plt.ylim()[1]-1) plt.legend(loc="right", fontsize=10) plt.title("Sorting $10^{}$ elements".format(round(math.log(size, 10)))) figure = plt.gcf() figure.set_size_inches(8*.75, 6*.75) plt.savefig(os.path.join("plots", "{}_{}.png".format(os.path.splitext(filename)[0], size)), dpi = 100, bbox_inches="tight") plt.clf() pdqsort-0.0.0+git20180419/bench/bench.cpp000066400000000000000000000124671326615704000174730ustar00rootroot00000000000000#include #include #include #include #include #include #include #include #include #include #include "../pdqsort.h" #include "timsort.h" #ifdef _WIN32 #include #define rdtsc __rdtsc #else #ifdef __i586__ static __inline__ unsigned long long rdtsc() { unsigned long long int x; __asm__ volatile(".byte 0x0f, 0x31" : "=A" (x)); return x; } #elif defined(__x86_64__) static __inline__ unsigned long long rdtsc(){ unsigned hi, lo; __asm__ __volatile__("rdtsc" : "=a"(lo), "=d"(hi)); return ((unsigned long long) lo) | (((unsigned long long) hi) << 32); } #else #error no rdtsc implementation #endif #endif std::vector shuffled_int(int size, std::mt19937_64& rng) { std::vector v; v.reserve(size); for (int i = 0; i < size; ++i) v.push_back(i); std::shuffle(v.begin(), v.end(), rng); return v; } std::vector shuffled_16_values_int(int size, std::mt19937_64& rng) { std::vector v; v.reserve(size); for (int i = 0; i < size; ++i) v.push_back(i % 16); std::shuffle(v.begin(), v.end(), rng); return v; } std::vector all_equal_int(int size, std::mt19937_64&) { std::vector v; v.reserve(size); for (int i = 0; i < size; ++i) v.push_back(0); return v; } std::vector ascending_int(int size, std::mt19937_64&) { std::vector v; v.reserve(size); for (int i = 0; i < size; ++i) v.push_back(i); return v; } std::vector descending_int(int size, std::mt19937_64&) { std::vector v; v.reserve(size); for (int i = size - 1; i >= 0; --i) v.push_back(i); return v; } std::vector pipe_organ_int(int size, std::mt19937_64&) { std::vector v; v.reserve(size); for (int i = 0; i < size/2; ++i) v.push_back(i); for (int i = size/2; i < size; ++i) v.push_back(size - i); return v; } std::vector push_front_int(int size, std::mt19937_64&) { std::vector v; v.reserve(size); for (int i = 1; i < size; ++i) v.push_back(i); v.push_back(0); return v; } std::vector push_middle_int(int size, std::mt19937_64&) { std::vector v; v.reserve(size); for (int i = 0; i < size; ++i) { if (i != size/2) v.push_back(i); } v.push_back(size/2); return v; } template void heapsort(Iter begin, Iter end, Compare comp) { std::make_heap(begin, end, comp); std::sort_heap(begin, end, comp); } int main() { auto seed = std::time(0); std::mt19937_64 el; typedef std::vector (*DistrF)(int, std::mt19937_64&); typedef void (*SortF)(std::vector::iterator, std::vector::iterator, std::less); std::pair distributions[] = { {"shuffled_int", shuffled_int}, {"shuffled_16_values_int", shuffled_16_values_int}, {"all_equal_int", all_equal_int}, {"ascending_int", ascending_int}, {"descending_int", descending_int}, {"pipe_organ_int", pipe_organ_int}, {"push_front_int", push_front_int}, {"push_middle_int", push_middle_int} }; std::pair sorts[] = { {"pdqsort", &pdqsort::iterator, std::less>}, {"std::sort", &std::sort::iterator, std::less>}, {"std::stable_sort", &std::stable_sort::iterator, std::less>}, // {"std::sort_heap", &heapsort::iterator, std::less>}, // {"timsort", &gfx::timsort::iterator, std::less>} }; int sizes[] = {1000000, 100}; for (auto& distribution : distributions) { for (auto& sort : sorts) { el.seed(seed); for (auto size : sizes) { std::chrono::time_point total_start, total_end; std::vector cycles; total_start = std::chrono::high_resolution_clock::now(); total_end = std::chrono::high_resolution_clock::now(); while (std::chrono::duration_cast(total_end - total_start).count() < 5000) { std::vector v = distribution.second(size, el); uint64_t start = rdtsc(); sort.second(v.begin(), v.end(), std::less()); uint64_t end = rdtsc(); cycles.push_back(uint64_t(double(end - start) / size + 0.5)); total_end = std::chrono::high_resolution_clock::now(); // if (!std::is_sorted(v.begin(), v.end())) { // std::cerr << "sort failed: "; // std::cerr << size << " " << distribution.first << " " << sort.first << "\n"; // } } std::sort(cycles.begin(), cycles.end()); std::cerr << size << " " << distribution.first << " " << sort.first << " " << cycles[cycles.size()/2] << "\n"; std::cout << size << " " << distribution.first << " " << sort.first << " " << cycles[cycles.size()/2] << "\n"; } } } return 0; } pdqsort-0.0.0+git20180419/bench/boxplot.py000066400000000000000000000057341326615704000177500ustar00rootroot00000000000000import math import os from matplotlib import pyplot as plt distribution_names = { "shuffled_16_values_int": "Shuffled (16 values)", "shuffled_int": "Shuffled", "all_equal_int": "All equal", "ascending_int": "Ascending", "descending_int": "Descending", "pipe_organ_int": "Pipe organ", "push_front_int": "Push front", "push_middle_int": "Push middle" } for filename in os.listdir("profiles"): data = {} for line in open(os.path.join("profiles", filename)): size, distribution, algo, *results = line.split() size = int(size) distribution = distribution_names[distribution] results = [int(result) for result in results] if not size in data: data[size] = {} if not distribution in data[size]: data[size][distribution] = {} data[size][distribution][algo] = results def skin_boxplot(bp): plt.setp(bp["boxes"], color="black") plt.setp(bp["fliers"], color="black") plt.setp(bp["whiskers"], color="black") plt.setp(bp["medians"], color="black", linewidth=3, solid_capstyle="butt") size = 10**6 distributions = ("Shuffled", "Shuffled (16 values)", "All equal", "Ascending", "Descending", "Pipe organ", "Push front", "Push middle") algos = ("heapsort", "introsort", "pdqsort") if "timsort" in data[size]["Shuffled"]: algos += ("timsort",) groupnames = distributions groupsize = len(algos) groups = [[data[size][distribution][algo] for algo in algos] for distribution in distributions] for i, group in enumerate(groups): skin_boxplot(plt.boxplot(group, positions=[.75 + (0.5 + groupsize)*i + n for n in range(groupsize)], widths=0.6)) # Set axes limits and labels. plt.margins(0.1) plt.xticks([0.25 + (groupsize)/2 + (0.5 + groupsize) * n for n in range(len(groupnames))], groupnames) plt.xlim(0, (0.5 + groupsize) * len(groups)) plt.ylim(0, plt.ylim()[1] * 1.05) plt.xlabel("Distribution") plt.ylabel("Cycles per element") # Turn off ticks for x-axis. plt.tick_params( axis="x", which="both", bottom="off", top="off", labelbottom="on" ) ax = plt.gca() for i in range(len(groups)): for n in range(groupsize): ax.text(0.75 + (0.5 + groupsize) * i + n, plt.ylim()[1] * 0.92, str(n + 1), horizontalalignment="center", size="x-small") for i in range(len(groups) - 1): x = (1 + i) * (0.5 + groupsize) plt.plot((x, x), (0, plt.ylim()[1] * 0.95), "-", color="lightgrey") plt.plot((0, plt.xlim()[1]), (plt.ylim()[1] * 0.95, plt.ylim()[1] * 0.95), "-", color="lightgrey") plt.title("Sorting $10^{}$elements".format(round(math.log(size, 10)))) plt.suptitle(" ".join("({}) {}".format(n + 1, algo) for n, algo in enumerate(algos)), y=.885) figure = plt.gcf() # get current figure figure.set_size_inches(19.2, 10.8) plt.savefig(os.path.join("plots", os.path.splitext(filename)[0] + ".png"), dpi = 100) plt.clf() pdqsort-0.0.0+git20180419/bench/readme.txt000066400000000000000000000005051326615704000176740ustar00rootroot00000000000000This is the benchmark I used to test pdqsort and co. bench.cpp just spits out the raw cycle counts, I use Python for post-processing, such as making a bar graph of the median cycle count. Example: g++ -std=c++11 -O2 -m64 -march=native bench.cpp ./a.out > profiles/pdqsort.txt python3 bars.py "i5-4670k @ 3.4GHz"pdqsort-0.0.0+git20180419/bench/timsort.h000066400000000000000000000474241326615704000175630ustar00rootroot00000000000000/* * C++ implementation of timsort * * ported from Python's and OpenJDK's: * - http://svn.python.org/projects/python/trunk/Objects/listobject.c * - http://cr.openjdk.java.net/~martin/webrevs/openjdk7/timsort/raw_files/new/src/share/classes/java/util/TimSort.java * * Copyright (c) 2011 Fuji, Goro (gfx) . * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to * deal in the Software without restriction, including without limitation the * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or * sell copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS * IN THE SOFTWARE. */ #ifndef GFX_TIMSORT_HPP #define GFX_TIMSORT_HPP #include #include #include #include #include #ifdef ENABLE_TIMSORT_LOG #include #define GFX_TIMSORT_LOG(expr) (std::clog << "# " << __func__ << ": " << expr << std::endl) #else #define GFX_TIMSORT_LOG(expr) ((void)0) #endif #if ENABLE_STD_MOVE && __cplusplus >= 201103L #define GFX_TIMSORT_MOVE(x) std::move(x) #else #define GFX_TIMSORT_MOVE(x) (x) #endif namespace gfx { // --------------------------------------- // Declaration // --------------------------------------- /** * Same as std::stable_sort(first, last). */ template inline void timsort(RandomAccessIterator const first, RandomAccessIterator const last); /** * Same as std::stable_sort(first, last, c). */ template inline void timsort(RandomAccessIterator const first, RandomAccessIterator const last, LessFunction compare); // --------------------------------------- // Implementation // --------------------------------------- template class Compare { public: typedef Value value_type; typedef LessFunction func_type; Compare(LessFunction f) : less_(f) { } Compare(const Compare& other) : less_(other.less_) { } bool lt(value_type x, value_type y) { return less_(x, y); } bool le(value_type x, value_type y) { return less_(x, y) || !less_(y, x); } bool gt(value_type x, value_type y) { return !less_(x, y) && less_(y, x); } bool ge(value_type x, value_type y) { return !less_(x, y); } func_type& less_function() { return less_; } private: func_type less_; }; template class TimSort { typedef RandomAccessIterator iter_t; typedef typename std::iterator_traits::value_type value_t; typedef typename std::iterator_traits::reference ref_t; typedef typename std::iterator_traits::difference_type diff_t; typedef Compare compare_t; static const int MIN_MERGE = 32; compare_t comp_; static const int MIN_GALLOP = 7; int minGallop_; // default to MIN_GALLOP std::vector tmp_; // temp storage for merges typedef typename std::vector::iterator tmp_iter_t; struct run { iter_t base; diff_t len; run(iter_t const b, diff_t const l) : base(b), len(l) { } }; std::vector pending_; static void sort(iter_t const lo, iter_t const hi, compare_t c) { assert( lo <= hi ); diff_t nRemaining = (hi - lo); if(nRemaining < 2) { return; // nothing to do } if(nRemaining < MIN_MERGE) { diff_t const initRunLen = countRunAndMakeAscending(lo, hi, c); GFX_TIMSORT_LOG("initRunLen: " << initRunLen); binarySort(lo, hi, lo + initRunLen, c); return; } TimSort ts(c); diff_t const minRun = minRunLength(nRemaining); iter_t cur = lo; do { diff_t runLen = countRunAndMakeAscending(cur, hi, c); if(runLen < minRun) { diff_t const force = std::min(nRemaining, minRun); binarySort(cur, cur + force, cur + runLen, c); runLen = force; } ts.pushRun(cur, runLen); ts.mergeCollapse(); cur += runLen; nRemaining -= runLen; } while(nRemaining != 0); assert( cur == hi ); ts.mergeForceCollapse(); assert( ts.pending_.size() == 1 ); GFX_TIMSORT_LOG("size: " << (hi - lo) << " tmp_.size(): " << ts.tmp_.size() << " pending_.size(): " << ts.pending_.size()); } // sort() static void binarySort(iter_t const lo, iter_t const hi, iter_t start, compare_t compare) { assert( lo <= start && start <= hi ); if(start == lo) { ++start; } for( ; start < hi; ++start ) { assert(lo <= start); /*const*/ value_t pivot = GFX_TIMSORT_MOVE(*start); iter_t const pos = std::upper_bound(lo, start, pivot, compare.less_function()); for(iter_t p = start; p > pos; --p) { *p = GFX_TIMSORT_MOVE(*(p - 1)); } *pos = GFX_TIMSORT_MOVE(pivot); } } static diff_t countRunAndMakeAscending(iter_t const lo, iter_t const hi, compare_t compare) { assert( lo < hi ); iter_t runHi = lo + 1; if( runHi == hi ) { return 1; } if(compare.lt(*(runHi++), *lo)) { // descending while(runHi < hi && compare.lt(*runHi, *(runHi - 1))) { ++runHi; } std::reverse(lo, runHi); } else { // ascending while(runHi < hi && compare.ge(*runHi, *(runHi - 1))) { ++runHi; } } return runHi - lo; } static diff_t minRunLength(diff_t n) { assert( n >= 0 ); diff_t r = 0; while(n >= MIN_MERGE) { r |= (n & 1); n >>= 1; } return n + r; } TimSort(compare_t c) : comp_(c), minGallop_(MIN_GALLOP) { } void pushRun(iter_t const runBase, diff_t const runLen) { pending_.push_back(run(runBase, runLen)); } void mergeCollapse() { while( pending_.size() > 1 ) { diff_t n = pending_.size() - 2; if(n > 0 && pending_[n - 1].len <= pending_[n].len + pending_[n + 1].len) { if(pending_[n - 1].len < pending_[n + 1].len) { --n; } mergeAt(n); } else if(pending_[n].len <= pending_[n + 1].len) { mergeAt(n); } else { break; } } } void mergeForceCollapse() { while( pending_.size() > 1 ) { diff_t n = pending_.size() - 2; if(n > 0 && pending_[n - 1].len < pending_[n + 1].len) { --n; } mergeAt(n); } } void mergeAt(diff_t const i) { diff_t const stackSize = pending_.size(); assert( stackSize >= 2 ); assert( i >= 0 ); assert( i == stackSize - 2 || i == stackSize - 3 ); iter_t base1 = pending_[i].base; diff_t len1 = pending_[i].len; iter_t base2 = pending_[i + 1].base; diff_t len2 = pending_[i + 1].len; assert( len1 > 0 && len2 > 0 ); assert( base1 + len1 == base2 ); pending_[i].len = len1 + len2; if(i == stackSize - 3) { pending_[i + 1] = pending_[i + 2]; } pending_.pop_back(); diff_t const k = gallopRight(*base2, base1, len1, 0); assert( k >= 0 ); base1 += k; len1 -= k; if(len1 == 0) { return; } len2 = gallopLeft(*(base1 + (len1 - 1)), base2, len2, len2 - 1); assert( len2 >= 0 ); if(len2 == 0) { return; } if(len1 <= len2) { mergeLo(base1, len1, base2, len2); } else { mergeHi(base1, len1, base2, len2); } } template diff_t gallopLeft(ref_t key, Iter const base, diff_t const len, diff_t const hint) { assert( len > 0 && hint >= 0 && hint < len ); diff_t lastOfs = 0; diff_t ofs = 1; if(comp_.gt(key, *(base + hint))) { diff_t const maxOfs = len - hint; while(ofs < maxOfs && comp_.gt(key, *(base + (hint + ofs)))) { lastOfs = ofs; ofs = (ofs << 1) + 1; if(ofs <= 0) { // int overflow ofs = maxOfs; } } if(ofs > maxOfs) { ofs = maxOfs; } lastOfs += hint; ofs += hint; } else { diff_t const maxOfs = hint + 1; while(ofs < maxOfs && comp_.le(key, *(base + (hint - ofs)))) { lastOfs = ofs; ofs = (ofs << 1) + 1; if(ofs <= 0) { ofs = maxOfs; } } if(ofs > maxOfs) { ofs = maxOfs; } diff_t const tmp = lastOfs; lastOfs = hint - ofs; ofs = hint - tmp; } assert( -1 <= lastOfs && lastOfs < ofs && ofs <= len ); return std::lower_bound(base+(lastOfs+1), base+ofs, key, comp_.less_function()) - base; } template diff_t gallopRight(ref_t key, Iter const base, diff_t const len, diff_t const hint) { assert( len > 0 && hint >= 0 && hint < len ); diff_t ofs = 1; diff_t lastOfs = 0; if(comp_.lt(key, *(base + hint))) { diff_t const maxOfs = hint + 1; while(ofs < maxOfs && comp_.lt(key, *(base + (hint - ofs)))) { lastOfs = ofs; ofs = (ofs << 1) + 1; if(ofs <= 0) { ofs = maxOfs; } } if(ofs > maxOfs) { ofs = maxOfs; } diff_t const tmp = lastOfs; lastOfs = hint - ofs; ofs = hint - tmp; } else { diff_t const maxOfs = len - hint; while(ofs < maxOfs && comp_.ge(key, *(base + (hint + ofs)))) { lastOfs = ofs; ofs = (ofs << 1) + 1; if(ofs <= 0) { // int overflow ofs = maxOfs; } } if(ofs > maxOfs) { ofs = maxOfs; } lastOfs += hint; ofs += hint; } assert( -1 <= lastOfs && lastOfs < ofs && ofs <= len ); return std::upper_bound(base+(lastOfs+1), base+ofs, key, comp_.less_function()) - base; } void mergeLo(iter_t const base1, diff_t len1, iter_t const base2, diff_t len2) { assert( len1 > 0 && len2 > 0 && base1 + len1 == base2 ); copy_to_tmp(base1, len1); tmp_iter_t cursor1 = tmp_.begin(); iter_t cursor2 = base2; iter_t dest = base1; *(dest++) = *(cursor2++); if(--len2 == 0) { std::copy(cursor1, cursor1 + len1, dest); return; } if(len1 == 1) { std::copy(cursor2, cursor2 + len2, dest); *(dest + len2) = *cursor1; return; } int minGallop(minGallop_); // outer: while(true) { int count1 = 0; int count2 = 0; bool break_outer = false; do { assert( len1 > 1 && len2 > 0 ); if(comp_.lt(*cursor2, *cursor1)) { *(dest++) = *(cursor2++); ++count2; count1 = 0; if(--len2 == 0) { break_outer = true; break; } } else { *(dest++) = *(cursor1++); ++count1; count2 = 0; if(--len1 == 1) { break_outer = true; break; } } } while( (count1 | count2) < minGallop ); if(break_outer) { break; } do { assert( len1 > 1 && len2 > 0 ); count1 = gallopRight(*cursor2, cursor1, len1, 0); if(count1 != 0) { std::copy_backward(cursor1, cursor1 + count1, dest + count1); dest += count1; cursor1 += count1; len1 -= count1; if(len1 <= 1) { break_outer = true; break; } } *(dest++) = *(cursor2++); if(--len2 == 0) { break_outer = true; break; } count2 = gallopLeft(*cursor1, cursor2, len2, 0); if(count2 != 0) { std::copy(cursor2, cursor2 + count2, dest); dest += count2; cursor2 += count2; len2 -= count2; if(len2 == 0) { break_outer = true; break; } } *(dest++) = *(cursor1++); if(--len1 == 1) { break_outer = true; break; } --minGallop; } while( (count1 >= MIN_GALLOP) | (count2 >= MIN_GALLOP) ); if(break_outer) { break; } if(minGallop < 0) { minGallop = 0; } minGallop += 2; } // end of "outer" loop minGallop_ = std::min(minGallop, 1); if(len1 == 1) { assert( len2 > 0 ); std::copy(cursor2, cursor2 + len2, dest); *(dest + len2) = *cursor1; } else { assert( len1 != 0 && "Comparision function violates its general contract"); assert( len2 == 0 ); assert( len1 > 1 ); std::copy(cursor1, cursor1 + len1, dest); } } void mergeHi(iter_t const base1, diff_t len1, iter_t const base2, diff_t len2) { assert( len1 > 0 && len2 > 0 && base1 + len1 == base2 ); copy_to_tmp(base2, len2); iter_t cursor1 = base1 + (len1 - 1); tmp_iter_t cursor2 = tmp_.begin() + (len2 - 1); iter_t dest = base2 + (len2 - 1); *(dest--) = *(cursor1--); if(--len1 == 0) { std::copy(tmp_.begin(), tmp_.begin() + len2, dest - (len2 - 1)); return; } if(len2 == 1) { dest -= len1; cursor1 -= len1; std::copy_backward(cursor1 + 1, cursor1 + (1 + len1), dest + (1 + len1)); *dest = *cursor2; return; } int minGallop( minGallop_ ); // outer: while(true) { int count1 = 0; int count2 = 0; bool break_outer = false; do { assert( len1 > 0 && len2 > 1 ); if(comp_.lt(*cursor2, *cursor1)) { *(dest--) = *(cursor1--); ++count1; count2 = 0; if(--len1 == 0) { break_outer = true; break; } } else { *(dest--) = *(cursor2--); ++count2; count1 = 0; if(--len2 == 1) { break_outer = true; break; } } } while( (count1 | count2) < minGallop ); if(break_outer) { break; } do { assert( len1 > 0 && len2 > 1 ); count1 = len1 - gallopRight(*cursor2, base1, len1, len1 - 1); if(count1 != 0) { dest -= count1; cursor1 -= count1; len1 -= count1; std::copy_backward(cursor1 + 1, cursor1 + (1 + count1), dest + (1 + count1)); if(len1 == 0) { break_outer = true; break; } } *(dest--) = *(cursor2--); if(--len2 == 1) { break_outer = true; break; } count2 = len2 - gallopLeft(*cursor1, tmp_.begin(), len2, len2 - 1); if(count2 != 0) { dest -= count2; cursor2 -= count2; len2 -= count2; std::copy(cursor2 + 1, cursor2 + (1 + count2), dest + 1); if(len2 <= 1) { break_outer = true; break; } } *(dest--) = *(cursor1--); if(--len1 == 0) { break_outer = true; break; } minGallop--; } while( (count1 >= MIN_GALLOP) | (count2 >= MIN_GALLOP) ); if(break_outer) { break; } if(minGallop < 0) { minGallop = 0; } minGallop += 2; } // end of "outer" loop minGallop_ = std::min(minGallop, 1); if(len2 == 1) { assert( len1 > 0 ); dest -= len1; cursor1 -= len1; std::copy_backward(cursor1 + 1, cursor1 + (1 + len1), dest + (1 + len1)); *dest = *cursor2; } else { assert( len2 != 0 && "Comparision function violates its general contract"); assert( len1 == 0 ); assert( len2 > 1 ); std::copy(tmp_.begin(), tmp_.begin() + len2, dest - (len2 - 1)); } } void copy_to_tmp(iter_t const begin, diff_t const len) { tmp_.clear(); tmp_.reserve(len); std::copy(begin, begin + len, std::back_inserter(tmp_)); } // the only interface is the friend timsort() function template friend void timsort(IterT first, IterT last, LessT c); }; template inline void timsort(RandomAccessIterator const first, RandomAccessIterator const last) { typedef typename std::iterator_traits::value_type value_type; timsort(first, last, std::less()); } template inline void timsort(RandomAccessIterator const first, RandomAccessIterator const last, LessFunction compare) { TimSort::sort(first, last, compare); } } // namespace gfx #undef GFX_TIMSORT_LOG #undef GFX_TIMSORT_MOVE #endif // GFX_TIMSORT_HPP pdqsort-0.0.0+git20180419/license.txt000066400000000000000000000015521326615704000170050ustar00rootroot00000000000000Copyright (c) 2015 Orson Peters This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. pdqsort-0.0.0+git20180419/pdqsort.h000066400000000000000000000543311326615704000164720ustar00rootroot00000000000000/* pdqsort.h - Pattern-defeating quicksort. Copyright (c) 2015 Orson Peters This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. */ #ifndef PDQSORT_H #define PDQSORT_H #include #include #include #include #include #if __cplusplus >= 201103L #include #include #define PDQSORT_PREFER_MOVE(x) std::move(x) #else #define PDQSORT_PREFER_MOVE(x) (x) #endif namespace pdqsort_detail { enum { // Partitions below this size are sorted using insertion sort. insertion_sort_threshold = 24, // Partitions above this size use Tukey's ninther to select the pivot. ninther_threshold = 128, // When we detect an already sorted partition, attempt an insertion sort that allows this // amount of element moves before giving up. partial_insertion_sort_limit = 8, // Must be multiple of 8 due to loop unrolling, and < 256 to fit in unsigned char. block_size = 64, // Cacheline size, assumes power of two. cacheline_size = 64 }; #if __cplusplus >= 201103L template struct is_default_compare : std::false_type { }; template struct is_default_compare> : std::true_type { }; template struct is_default_compare> : std::true_type { }; #endif // Returns floor(log2(n)), assumes n > 0. template inline int log2(T n) { int log = 0; while (n >>= 1) ++log; return log; } // Sorts [begin, end) using insertion sort with the given comparison function. template inline void insertion_sort(Iter begin, Iter end, Compare comp) { typedef typename std::iterator_traits::value_type T; if (begin == end) return; for (Iter cur = begin + 1; cur != end; ++cur) { Iter sift = cur; Iter sift_1 = cur - 1; // Compare first so we can avoid 2 moves for an element already positioned correctly. if (comp(*sift, *sift_1)) { T tmp = PDQSORT_PREFER_MOVE(*sift); do { *sift-- = PDQSORT_PREFER_MOVE(*sift_1); } while (sift != begin && comp(tmp, *--sift_1)); *sift = PDQSORT_PREFER_MOVE(tmp); } } } // Sorts [begin, end) using insertion sort with the given comparison function. Assumes // *(begin - 1) is an element smaller than or equal to any element in [begin, end). template inline void unguarded_insertion_sort(Iter begin, Iter end, Compare comp) { typedef typename std::iterator_traits::value_type T; if (begin == end) return; for (Iter cur = begin + 1; cur != end; ++cur) { Iter sift = cur; Iter sift_1 = cur - 1; // Compare first so we can avoid 2 moves for an element already positioned correctly. if (comp(*sift, *sift_1)) { T tmp = PDQSORT_PREFER_MOVE(*sift); do { *sift-- = PDQSORT_PREFER_MOVE(*sift_1); } while (comp(tmp, *--sift_1)); *sift = PDQSORT_PREFER_MOVE(tmp); } } } // Attempts to use insertion sort on [begin, end). Will return false if more than // partial_insertion_sort_limit elements were moved, and abort sorting. Otherwise it will // successfully sort and return true. template inline bool partial_insertion_sort(Iter begin, Iter end, Compare comp) { typedef typename std::iterator_traits::value_type T; if (begin == end) return true; int limit = 0; for (Iter cur = begin + 1; cur != end; ++cur) { if (limit > partial_insertion_sort_limit) return false; Iter sift = cur; Iter sift_1 = cur - 1; // Compare first so we can avoid 2 moves for an element already positioned correctly. if (comp(*sift, *sift_1)) { T tmp = PDQSORT_PREFER_MOVE(*sift); do { *sift-- = PDQSORT_PREFER_MOVE(*sift_1); } while (sift != begin && comp(tmp, *--sift_1)); *sift = PDQSORT_PREFER_MOVE(tmp); limit += cur - sift; } } return true; } template inline void sort2(Iter a, Iter b, Compare comp) { if (comp(*b, *a)) std::iter_swap(a, b); } // Sorts the elements *a, *b and *c using comparison function comp. template inline void sort3(Iter a, Iter b, Iter c, Compare comp) { sort2(a, b, comp); sort2(b, c, comp); sort2(a, b, comp); } template inline T* align_cacheline(T* p) { #if defined(UINTPTR_MAX) && __cplusplus >= 201103L std::uintptr_t ip = reinterpret_cast(p); #else std::size_t ip = reinterpret_cast(p); #endif ip = (ip + cacheline_size - 1) & -cacheline_size; return reinterpret_cast(ip); } template inline void swap_offsets(Iter first, Iter last, unsigned char* offsets_l, unsigned char* offsets_r, int num, bool use_swaps) { typedef typename std::iterator_traits::value_type T; if (use_swaps) { // This case is needed for the descending distribution, where we need // to have proper swapping for pdqsort to remain O(n). for (int i = 0; i < num; ++i) { std::iter_swap(first + offsets_l[i], last - offsets_r[i]); } } else if (num > 0) { Iter l = first + offsets_l[0]; Iter r = last - offsets_r[0]; T tmp(PDQSORT_PREFER_MOVE(*l)); *l = PDQSORT_PREFER_MOVE(*r); for (int i = 1; i < num; ++i) { l = first + offsets_l[i]; *r = PDQSORT_PREFER_MOVE(*l); r = last - offsets_r[i]; *l = PDQSORT_PREFER_MOVE(*r); } *r = PDQSORT_PREFER_MOVE(tmp); } } // Partitions [begin, end) around pivot *begin using comparison function comp. Elements equal // to the pivot are put in the right-hand partition. Returns the position of the pivot after // partitioning and whether the passed sequence already was correctly partitioned. Assumes the // pivot is a median of at least 3 elements and that [begin, end) is at least // insertion_sort_threshold long. Uses branchless partitioning. template inline std::pair partition_right_branchless(Iter begin, Iter end, Compare comp) { typedef typename std::iterator_traits::value_type T; // Move pivot into local for speed. T pivot(PDQSORT_PREFER_MOVE(*begin)); Iter first = begin; Iter last = end; // Find the first element greater than or equal than the pivot (the median of 3 guarantees // this exists). while (comp(*++first, pivot)); // Find the first element strictly smaller than the pivot. We have to guard this search if // there was no element before *first. if (first - 1 == begin) while (first < last && !comp(*--last, pivot)); else while ( !comp(*--last, pivot)); // If the first pair of elements that should be swapped to partition are the same element, // the passed in sequence already was correctly partitioned. bool already_partitioned = first >= last; if (!already_partitioned) { std::iter_swap(first, last); ++first; } // The following branchless partitioning is derived from "BlockQuicksort: How Branch // Mispredictions don’t affect Quicksort" by Stefan Edelkamp and Armin Weiss. unsigned char offsets_l_storage[block_size + cacheline_size]; unsigned char offsets_r_storage[block_size + cacheline_size]; unsigned char* offsets_l = align_cacheline(offsets_l_storage); unsigned char* offsets_r = align_cacheline(offsets_r_storage); int num_l, num_r, start_l, start_r; num_l = num_r = start_l = start_r = 0; while (last - first > 2 * block_size) { // Fill up offset blocks with elements that are on the wrong side. if (num_l == 0) { start_l = 0; Iter it = first; for (unsigned char i = 0; i < block_size;) { offsets_l[num_l] = i++; num_l += !comp(*it, pivot); ++it; offsets_l[num_l] = i++; num_l += !comp(*it, pivot); ++it; offsets_l[num_l] = i++; num_l += !comp(*it, pivot); ++it; offsets_l[num_l] = i++; num_l += !comp(*it, pivot); ++it; offsets_l[num_l] = i++; num_l += !comp(*it, pivot); ++it; offsets_l[num_l] = i++; num_l += !comp(*it, pivot); ++it; offsets_l[num_l] = i++; num_l += !comp(*it, pivot); ++it; offsets_l[num_l] = i++; num_l += !comp(*it, pivot); ++it; } } if (num_r == 0) { start_r = 0; Iter it = last; for (unsigned char i = 0; i < block_size;) { offsets_r[num_r] = ++i; num_r += comp(*--it, pivot); offsets_r[num_r] = ++i; num_r += comp(*--it, pivot); offsets_r[num_r] = ++i; num_r += comp(*--it, pivot); offsets_r[num_r] = ++i; num_r += comp(*--it, pivot); offsets_r[num_r] = ++i; num_r += comp(*--it, pivot); offsets_r[num_r] = ++i; num_r += comp(*--it, pivot); offsets_r[num_r] = ++i; num_r += comp(*--it, pivot); offsets_r[num_r] = ++i; num_r += comp(*--it, pivot); } } // Swap elements and update block sizes and first/last boundaries. int num = std::min(num_l, num_r); swap_offsets(first, last, offsets_l + start_l, offsets_r + start_r, num, num_l == num_r); num_l -= num; num_r -= num; start_l += num; start_r += num; if (num_l == 0) first += block_size; if (num_r == 0) last -= block_size; } int l_size = 0, r_size = 0; int unknown_left = (last - first) - ((num_r || num_l) ? block_size : 0); if (num_r) { // Handle leftover block by assigning the unknown elements to the other block. l_size = unknown_left; r_size = block_size; } else if (num_l) { l_size = block_size; r_size = unknown_left; } else { // No leftover block, split the unknown elements in two blocks. l_size = unknown_left/2; r_size = unknown_left - l_size; } // Fill offset buffers if needed. if (unknown_left && !num_l) { start_l = 0; Iter it = first; for (unsigned char i = 0; i < l_size;) { offsets_l[num_l] = i++; num_l += !comp(*it, pivot); ++it; } } if (unknown_left && !num_r) { start_r = 0; Iter it = last; for (unsigned char i = 0; i < r_size;) { offsets_r[num_r] = ++i; num_r += comp(*--it, pivot); } } int num = std::min(num_l, num_r); swap_offsets(first, last, offsets_l + start_l, offsets_r + start_r, num, num_l == num_r); num_l -= num; num_r -= num; start_l += num; start_r += num; if (num_l == 0) first += l_size; if (num_r == 0) last -= r_size; // We have now fully identified [first, last)'s proper position. Swap the last elements. if (num_l) { offsets_l += start_l; while (num_l--) std::iter_swap(first + offsets_l[num_l], --last); first = last; } if (num_r) { offsets_r += start_r; while (num_r--) std::iter_swap(last - offsets_r[num_r], first), ++first; last = first; } // Put the pivot in the right place. Iter pivot_pos = first - 1; *begin = PDQSORT_PREFER_MOVE(*pivot_pos); *pivot_pos = PDQSORT_PREFER_MOVE(pivot); return std::make_pair(pivot_pos, already_partitioned); } // Partitions [begin, end) around pivot *begin using comparison function comp. Elements equal // to the pivot are put in the right-hand partition. Returns the position of the pivot after // partitioning and whether the passed sequence already was correctly partitioned. Assumes the // pivot is a median of at least 3 elements and that [begin, end) is at least // insertion_sort_threshold long. template inline std::pair partition_right(Iter begin, Iter end, Compare comp) { typedef typename std::iterator_traits::value_type T; // Move pivot into local for speed. T pivot(PDQSORT_PREFER_MOVE(*begin)); Iter first = begin; Iter last = end; // Find the first element greater than or equal than the pivot (the median of 3 guarantees // this exists). while (comp(*++first, pivot)); // Find the first element strictly smaller than the pivot. We have to guard this search if // there was no element before *first. if (first - 1 == begin) while (first < last && !comp(*--last, pivot)); else while ( !comp(*--last, pivot)); // If the first pair of elements that should be swapped to partition are the same element, // the passed in sequence already was correctly partitioned. bool already_partitioned = first >= last; // Keep swapping pairs of elements that are on the wrong side of the pivot. Previously // swapped pairs guard the searches, which is why the first iteration is special-cased // above. while (first < last) { std::iter_swap(first, last); while (comp(*++first, pivot)); while (!comp(*--last, pivot)); } // Put the pivot in the right place. Iter pivot_pos = first - 1; *begin = PDQSORT_PREFER_MOVE(*pivot_pos); *pivot_pos = PDQSORT_PREFER_MOVE(pivot); return std::make_pair(pivot_pos, already_partitioned); } // Similar function to the one above, except elements equal to the pivot are put to the left of // the pivot and it doesn't check or return if the passed sequence already was partitioned. // Since this is rarely used (the many equal case), and in that case pdqsort already has O(n) // performance, no block quicksort is applied here for simplicity. template inline Iter partition_left(Iter begin, Iter end, Compare comp) { typedef typename std::iterator_traits::value_type T; T pivot(PDQSORT_PREFER_MOVE(*begin)); Iter first = begin; Iter last = end; while (comp(pivot, *--last)); if (last + 1 == end) while (first < last && !comp(pivot, *++first)); else while ( !comp(pivot, *++first)); while (first < last) { std::iter_swap(first, last); while (comp(pivot, *--last)); while (!comp(pivot, *++first)); } Iter pivot_pos = last; *begin = PDQSORT_PREFER_MOVE(*pivot_pos); *pivot_pos = PDQSORT_PREFER_MOVE(pivot); return pivot_pos; } template inline void pdqsort_loop(Iter begin, Iter end, Compare comp, int bad_allowed, bool leftmost = true) { typedef typename std::iterator_traits::difference_type diff_t; // Use a while loop for tail recursion elimination. while (true) { diff_t size = end - begin; // Insertion sort is faster for small arrays. if (size < insertion_sort_threshold) { if (leftmost) insertion_sort(begin, end, comp); else unguarded_insertion_sort(begin, end, comp); return; } // Choose pivot as median of 3 or pseudomedian of 9. diff_t s2 = size / 2; if (size > ninther_threshold) { sort3(begin, begin + s2, end - 1, comp); sort3(begin + 1, begin + (s2 - 1), end - 2, comp); sort3(begin + 2, begin + (s2 + 1), end - 3, comp); sort3(begin + (s2 - 1), begin + s2, begin + (s2 + 1), comp); std::iter_swap(begin, begin + s2); } else sort3(begin + s2, begin, end - 1, comp); // If *(begin - 1) is the end of the right partition of a previous partition operation // there is no element in [begin, end) that is smaller than *(begin - 1). Then if our // pivot compares equal to *(begin - 1) we change strategy, putting equal elements in // the left partition, greater elements in the right partition. We do not have to // recurse on the left partition, since it's sorted (all equal). if (!leftmost && !comp(*(begin - 1), *begin)) { begin = partition_left(begin, end, comp) + 1; continue; } // Partition and get results. std::pair part_result = Branchless ? partition_right_branchless(begin, end, comp) : partition_right(begin, end, comp); Iter pivot_pos = part_result.first; bool already_partitioned = part_result.second; // Check for a highly unbalanced partition. diff_t l_size = pivot_pos - begin; diff_t r_size = end - (pivot_pos + 1); bool highly_unbalanced = l_size < size / 8 || r_size < size / 8; // If we got a highly unbalanced partition we shuffle elements to break many patterns. if (highly_unbalanced) { // If we had too many bad partitions, switch to heapsort to guarantee O(n log n). if (--bad_allowed == 0) { std::make_heap(begin, end, comp); std::sort_heap(begin, end, comp); return; } if (l_size >= insertion_sort_threshold) { std::iter_swap(begin, begin + l_size / 4); std::iter_swap(pivot_pos - 1, pivot_pos - l_size / 4); if (l_size > ninther_threshold) { std::iter_swap(begin + 1, begin + (l_size / 4 + 1)); std::iter_swap(begin + 2, begin + (l_size / 4 + 2)); std::iter_swap(pivot_pos - 2, pivot_pos - (l_size / 4 + 1)); std::iter_swap(pivot_pos - 3, pivot_pos - (l_size / 4 + 2)); } } if (r_size >= insertion_sort_threshold) { std::iter_swap(pivot_pos + 1, pivot_pos + (1 + r_size / 4)); std::iter_swap(end - 1, end - r_size / 4); if (r_size > ninther_threshold) { std::iter_swap(pivot_pos + 2, pivot_pos + (2 + r_size / 4)); std::iter_swap(pivot_pos + 3, pivot_pos + (3 + r_size / 4)); std::iter_swap(end - 2, end - (1 + r_size / 4)); std::iter_swap(end - 3, end - (2 + r_size / 4)); } } } else { // If we were decently balanced and we tried to sort an already partitioned // sequence try to use insertion sort. if (already_partitioned && partial_insertion_sort(begin, pivot_pos, comp) && partial_insertion_sort(pivot_pos + 1, end, comp)) return; } // Sort the left partition first using recursion and do tail recursion elimination for // the right-hand partition. pdqsort_loop(begin, pivot_pos, comp, bad_allowed, leftmost); begin = pivot_pos + 1; leftmost = false; } } } template inline void pdqsort(Iter begin, Iter end, Compare comp) { if (begin == end) return; #if __cplusplus >= 201103L pdqsort_detail::pdqsort_loop::type>::value && std::is_arithmetic::value_type>::value>( begin, end, comp, pdqsort_detail::log2(end - begin)); #else pdqsort_detail::pdqsort_loop( begin, end, comp, pdqsort_detail::log2(end - begin)); #endif } template inline void pdqsort(Iter begin, Iter end) { typedef typename std::iterator_traits::value_type T; pdqsort(begin, end, std::less()); } template inline void pdqsort_branchless(Iter begin, Iter end, Compare comp) { if (begin == end) return; pdqsort_detail::pdqsort_loop( begin, end, comp, pdqsort_detail::log2(end - begin)); } template inline void pdqsort_branchless(Iter begin, Iter end) { typedef typename std::iterator_traits::value_type T; pdqsort_branchless(begin, end, std::less()); } #undef PDQSORT_PREFER_MOVE #endif pdqsort-0.0.0+git20180419/readme.md000066400000000000000000000142341326615704000164020ustar00rootroot00000000000000pdqsort ------- Pattern-defeating quicksort (pdqsort) is a novel sorting algorithm that combines the fast average case of randomized quicksort with the fast worst case of heapsort, while achieving linear time on inputs with certain patterns. pdqsort is an extension and improvement of David Mussers introsort. All code is available for free under the zlib license. Best Average Worst Memory Stable Deterministic n n log n n log n log n No Yes ### Usage `pdqsort` is a drop-in replacement for [`std::sort`](http://en.cppreference.com/w/cpp/algorithm/sort). Just replace a call to `std::sort` with `pdqsort` to start using pattern-defeating quicksort. If your comparison function is branchless, you can call `pdqsort_branchless` for a potential big speedup. If you are using C++11, the type you're sorting is arithmetic and your comparison function is not given or is `std::less`/`std::greater`, `pdqsort` automatically delegates to `pdqsort_branchless`. ### Benchmark A comparison of pdqsort and GCC's `std::sort` and `std::stable_sort` with various input distributions: ![Performance graph](http://i.imgur.com/1RnIGBO.png) Compiled with `-std=c++11 -O2 -m64 -march=native`. ### Visualization A visualization of pattern-defeating quicksort sorting a ~200 element array with some duplicates. Generated using Timo Bingmann's [The Sound of Sorting](http://panthema.net/2013/sound-of-sorting/) program, a tool that has been invaluable during the development of pdqsort. For the purposes of this visualization the cutoff point for insertion sort was lowered to 8 elements. ![Visualization](http://i.imgur.com/QzFG09F.gif) ### The best case pdqsort is designed to run in linear time for a couple of best-case patterns. Linear time is achieved for inputs that are in strictly ascending or descending order, only contain equal elements, or are strictly in ascending order followed by one out-of-place element. There are two separate mechanisms at play to achieve this. For equal elements a smart partitioning scheme is used that always puts equal elements in the partition containing elements greater than the pivot. When a new pivot is chosen it's compared to the greatest element in the partition before it. If they compare equal we can derive that there are no elements smaller than the chosen pivot. When this happens we switch strategy for this partition, and filter out all elements equal to the pivot. To get linear time for the other patterns we check after every partition if any swaps were made. If no swaps were made and the partition was decently balanced we will optimistically attempt to use insertion sort. This insertion sort aborts if more than a constant amount of moves are required to sort. ### The average case On average case data where no patterns are detected pdqsort is effectively a quicksort that uses median-of-3 pivot selection, switching to insertion sort if the number of elements to be (recursively) sorted is small. The overhead associated with detecting the patterns for the best case is so small it lies within the error of measurement. pdqsort gets a great speedup over the traditional way of implementing quicksort when sorting large arrays (1000+ elements). This is due to a new technique described in "BlockQuicksort: How Branch Mispredictions don't affect Quicksort" by Stefan Edelkamp and Armin Weiss. In short, we bypass the branch predictor by using small buffers (entirely in L1 cache) of the indices of elements that need to be swapped. We fill these buffers in a branch-free way that's quite elegant (in pseudocode): ```cpp buffer_num = 0; buffer_max_size = 64; for (int i = 0; i < buffer_max_size; ++i) { // With branch: if (elements[i] < pivot) { buffer[buffer_num] = i; buffer_num++; } // Without: buffer[buffer_num] = i; buffer_num += (elements[i] < pivot); } ``` This is only a speedup if the comparison function itself is branchless, however. By default pdqsort will detect this if you're using C++11 or higher, the type you're sorting is arithmetic (e.g. `int`), and you're using either `std::less` or `std::greater`. You can explicitly request branchless partitioning by calling `pdqsort_branchless` instead of `pdqsort`. ### The worst case Quicksort naturally performs bad on inputs that form patterns, due to it being a partition-based sort. Choosing a bad pivot will result in many comparisons that give little to no progress in the sorting process. If the pattern does not get broken up, this can happen many times in a row. Worse, real world data is filled with these patterns. Traditionally the solution to this is to randomize the pivot selection of quicksort. While this technically still allows for a quadratic worst case, the chances of it happening are astronomically small. Later, in introsort, pivot selection is kept deterministic, instead switching to the guaranteed O(n log n) heapsort if the recursion depth becomes too big. In pdqsort we adopt a hybrid approach, (deterministically) shuffling some elements to break up patterns when we encounter a "bad" partition. If we encounter too many "bad" partitions we switch to heapsort. ### Bad partitions A bad partition occurs when the position of the pivot after partitioning is under 12.5% (1/8th) percentile or over 87,5% percentile - the partition is highly unbalanced. When this happens we will shuffle four elements at fixed locations for both partitions. This effectively breaks up many patterns. If we encounter more than log(n) bad partitions we will switch to heapsort. The 1/8th percentile is not chosen arbitrarily. An upper bound of quicksorts worst case runtime can be approximated within a constant factor by the following recurrence: T(n, p) = n + T(p(n-1), p) + T((1-p)(n-1), p) Where n is the number of elements, and p is the percentile of the pivot after partitioning. `T(n, 1/2)` is the best case for quicksort. On modern systems heapsort is profiled to be approximately 1.8 to 2 times as slow as quicksort. Choosing p such that `T(n, 1/2) / T(n, p) ~= 1.9` as n gets big will ensure that we will only switch to heapsort if it would speed up the sorting. p = 1/8 is a reasonably close value and is cheap to compute on every platform using a bitshift.