{"id":13424486,"url":"https://github.com/orlp/pdqsort","last_synced_at":"2025-05-15T11:08:13.645Z","repository":{"id":27693711,"uuid":"31180321","full_name":"orlp/pdqsort","owner":"orlp","description":"Pattern-defeating quicksort.","archived":false,"fork":false,"pushed_at":"2023-12-06T02:22:11.000Z","size":88,"stargazers_count":2408,"open_issues_count":6,"forks_count":101,"subscribers_count":74,"default_branch":"master","last_synced_at":"2025-04-14T19:58:27.867Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"C++","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"zlib","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/orlp.png","metadata":{"files":{"readme":"readme.md","changelog":null,"contributing":null,"funding":null,"license":"license.txt","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null}},"created_at":"2015-02-22T21:01:03.000Z","updated_at":"2025-04-12T13:34:27.000Z","dependencies_parsed_at":"2022-07-12T04:00:28.844Z","dependency_job_id":"db74023e-5876-4a00-b285-5d050e5aa7b7","html_url":"https://github.com/orlp/pdqsort","commit_stats":{"total_commits":63,"total_committers":4,"mean_commits":15.75,"dds":0.04761904761904767,"last_synced_commit":"b1ef26a55cdb60d236a5cb199c4234c704f46726"},"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/orlp%2Fpdqsort","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/orlp%2Fpdqsort/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/orlp%2Fpdqsort/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/orlp%2Fpdqsort/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/orlp","download_url":"https://codeload.github.com/orlp/pdqsort/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":254328384,"owners_count":22052632,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-07-31T00:00:55.072Z","updated_at":"2025-05-15T11:08:13.617Z","avatar_url":"https://github.com/orlp.png","language":"C++","readme":"pdqsort\n-------\n\nPattern-defeating quicksort (pdqsort) is a novel sorting algorithm that combines the fast average\ncase of randomized quicksort with the fast worst case of heapsort, while achieving linear time on\ninputs with certain patterns. pdqsort is an extension and improvement of David Mussers introsort.\nAll code is available for free under the zlib license.\n\n    Best        Average     Worst       Memory      Stable      Deterministic\n    n           n log n     n log n     log n       No          Yes\n\n### Usage\n\n`pdqsort` is a drop-in replacement for [`std::sort`](http://en.cppreference.com/w/cpp/algorithm/sort).\nJust replace a call to `std::sort` with `pdqsort` to start using pattern-defeating quicksort. If your\ncomparison function is branchless, you can call `pdqsort_branchless` for a potential big speedup. If\nyou are using C++11, the type you're sorting is arithmetic and your comparison function is not given\nor is `std::less`/`std::greater`, `pdqsort` automatically delegates to `pdqsort_branchless`.\n\n### Benchmark\n\nA comparison of pdqsort and GCC's `std::sort` and `std::stable_sort` with various input\ndistributions:\n\n![Performance graph](http://i.imgur.com/1RnIGBO.png)\n\nCompiled with `-std=c++11 -O2 -m64 -march=native`.\n\n\n### Visualization\n\nA visualization of pattern-defeating quicksort sorting a ~200 element array with some duplicates.\nGenerated using Timo Bingmann's [The Sound of Sorting](http://panthema.net/2013/sound-of-sorting/)\nprogram, a tool that has been invaluable during the development of pdqsort. For the purposes of\nthis visualization the cutoff point for insertion sort was lowered to 8 elements.\n\n![Visualization](http://i.imgur.com/QzFG09F.gif)\n\n\n### The best case\n\npdqsort is designed to run in linear time for a couple of best-case patterns. Linear time is\nachieved for inputs that are in strictly ascending or descending order, only contain equal elements,\nor are strictly in ascending order followed by one out-of-place element. There are two separate\nmechanisms at play to achieve this.\n\nFor equal elements a smart partitioning scheme is used that always puts equal elements in the\npartition containing elements greater than the pivot. When a new pivot is chosen it's compared to\nthe greatest element in the partition before it. If they compare equal we can derive that there are\nno elements smaller than the chosen pivot. When this happens we switch strategy for this partition,\nand filter out all elements equal to the pivot.\n\nTo get linear time for the other patterns we check after every partition if any swaps were made. If\nno swaps were made and the partition was decently balanced we will optimistically attempt to use\ninsertion sort. This insertion sort aborts if more than a constant amount of moves are required to\nsort.\n\n\n### The average case\n\nOn average case data where no patterns are detected pdqsort is effectively a quicksort that uses\nmedian-of-3 pivot selection, switching to insertion sort if the number of elements to be\n(recursively) sorted is small. The overhead associated with detecting the patterns for the best case\nis so small it lies within the error of measurement.\n\npdqsort gets a great speedup over the traditional way of implementing quicksort when sorting large\narrays (1000+ elements). This is due to a new technique described in \"BlockQuicksort: How Branch\nMispredictions don't affect Quicksort\" by Stefan Edelkamp and Armin Weiss. In short, we bypass the\nbranch predictor by using small buffers (entirely in L1 cache) of the indices of elements that need\nto be swapped. We fill these buffers in a branch-free way that's quite elegant (in pseudocode):\n\n```cpp\nbuffer_num = 0; buffer_max_size = 64;\nfor (int i = 0; i \u003c buffer_max_size; ++i) {\n    // With branch:\n    if (elements[i] \u003c pivot) { buffer[buffer_num] = i; buffer_num++; }\n    // Without:\n    buffer[buffer_num] = i; buffer_num += (elements[i] \u003c pivot);\n}\n```\n\nThis is only a speedup if the comparison function itself is branchless, however. By default pdqsort\nwill detect this if you're using C++11 or higher, the type you're sorting is arithmetic (e.g.\n`int`), and you're using either `std::less` or `std::greater`. You can explicitly request branchless\npartitioning by calling `pdqsort_branchless` instead of `pdqsort`.\n\n\n### The worst case\n\nQuicksort naturally performs bad on inputs that form patterns, due to it being a partition-based\nsort. Choosing a bad pivot will result in many comparisons that give little to no progress in the\nsorting process. If the pattern does not get broken up, this can happen many times in a row. Worse,\nreal world data is filled with these patterns.\n\nTraditionally the solution to this is to randomize the pivot selection of quicksort. While this\ntechnically still allows for a quadratic worst case, the chances of it happening are astronomically\nsmall. Later, in introsort, pivot selection is kept deterministic, instead switching to the\nguaranteed O(n log n) heapsort if the recursion depth becomes too big. In pdqsort we adopt a hybrid\napproach, (deterministically) shuffling some elements to break up patterns when we encounter a \"bad\"\npartition. If we encounter too many \"bad\" partitions we switch to heapsort.\n\n\n### Bad partitions\n\nA bad partition occurs when the position of the pivot after partitioning is under 12.5% (1/8th)\npercentile or over 87,5% percentile - the partition is highly unbalanced. When this happens we will\nshuffle four elements at fixed locations for both partitions. This effectively breaks up many\npatterns. If we encounter more than log(n) bad partitions we will switch to heapsort.\n\nThe 1/8th percentile is not chosen arbitrarily. An upper bound of quicksorts worst case runtime can\nbe approximated within a constant factor by the following recurrence:\n\n    T(n, p) = n + T(p(n-1), p) + T((1-p)(n-1), p)\n\nWhere n is the number of elements, and p is the percentile of the pivot after partitioning.\n`T(n, 1/2)` is the best case for quicksort. On modern systems heapsort is profiled to be\napproximately 1.8 to 2 times as slow as quicksort. Choosing p such that `T(n, 1/2) / T(n, p) ~= 1.9`\nas n gets big will ensure that we will only switch to heapsort if it would speed up the sorting.\np = 1/8 is a reasonably close value and is cheap to compute on every platform using a bitshift.\n","funding_links":[],"categories":["Sorting","C++","Coding","排序","Containers and Algorithms"],"sub_categories":["C++ Data Structures and Algorithms","序列化"],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Forlp%2Fpdqsort","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Forlp%2Fpdqsort","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Forlp%2Fpdqsort/lists"}