1 | /* -*- Mode: js; js-indent-level: 2; -*- */
|
---|
2 | /*
|
---|
3 | * Copyright 2011 Mozilla Foundation and contributors
|
---|
4 | * Licensed under the New BSD license. See LICENSE or:
|
---|
5 | * http://opensource.org/licenses/BSD-3-Clause
|
---|
6 | */
|
---|
7 |
|
---|
8 | // It turns out that some (most?) JavaScript engines don't self-host
|
---|
9 | // `Array.prototype.sort`. This makes sense because C++ will likely remain
|
---|
10 | // faster than JS when doing raw CPU-intensive sorting. However, when using a
|
---|
11 | // custom comparator function, calling back and forth between the VM's C++ and
|
---|
12 | // JIT'd JS is rather slow *and* loses JIT type information, resulting in
|
---|
13 | // worse generated code for the comparator function than would be optimal. In
|
---|
14 | // fact, when sorting with a comparator, these costs outweigh the benefits of
|
---|
15 | // sorting in C++. By using our own JS-implemented Quick Sort (below), we get
|
---|
16 | // a ~3500ms mean speed-up in `bench/bench.html`.
|
---|
17 |
|
---|
18 | function SortTemplate(comparator) {
|
---|
19 |
|
---|
20 | /**
|
---|
21 | * Swap the elements indexed by `x` and `y` in the array `ary`.
|
---|
22 | *
|
---|
23 | * @param {Array} ary
|
---|
24 | * The array.
|
---|
25 | * @param {Number} x
|
---|
26 | * The index of the first item.
|
---|
27 | * @param {Number} y
|
---|
28 | * The index of the second item.
|
---|
29 | */
|
---|
30 | function swap(ary, x, y) {
|
---|
31 | var temp = ary[x];
|
---|
32 | ary[x] = ary[y];
|
---|
33 | ary[y] = temp;
|
---|
34 | }
|
---|
35 |
|
---|
36 | /**
|
---|
37 | * Returns a random integer within the range `low .. high` inclusive.
|
---|
38 | *
|
---|
39 | * @param {Number} low
|
---|
40 | * The lower bound on the range.
|
---|
41 | * @param {Number} high
|
---|
42 | * The upper bound on the range.
|
---|
43 | */
|
---|
44 | function randomIntInRange(low, high) {
|
---|
45 | return Math.round(low + (Math.random() * (high - low)));
|
---|
46 | }
|
---|
47 |
|
---|
48 | /**
|
---|
49 | * The Quick Sort algorithm.
|
---|
50 | *
|
---|
51 | * @param {Array} ary
|
---|
52 | * An array to sort.
|
---|
53 | * @param {function} comparator
|
---|
54 | * Function to use to compare two items.
|
---|
55 | * @param {Number} p
|
---|
56 | * Start index of the array
|
---|
57 | * @param {Number} r
|
---|
58 | * End index of the array
|
---|
59 | */
|
---|
60 | function doQuickSort(ary, comparator, p, r) {
|
---|
61 | // If our lower bound is less than our upper bound, we (1) partition the
|
---|
62 | // array into two pieces and (2) recurse on each half. If it is not, this is
|
---|
63 | // the empty array and our base case.
|
---|
64 |
|
---|
65 | if (p < r) {
|
---|
66 | // (1) Partitioning.
|
---|
67 | //
|
---|
68 | // The partitioning chooses a pivot between `p` and `r` and moves all
|
---|
69 | // elements that are less than or equal to the pivot to the before it, and
|
---|
70 | // all the elements that are greater than it after it. The effect is that
|
---|
71 | // once partition is done, the pivot is in the exact place it will be when
|
---|
72 | // the array is put in sorted order, and it will not need to be moved
|
---|
73 | // again. This runs in O(n) time.
|
---|
74 |
|
---|
75 | // Always choose a random pivot so that an input array which is reverse
|
---|
76 | // sorted does not cause O(n^2) running time.
|
---|
77 | var pivotIndex = randomIntInRange(p, r);
|
---|
78 | var i = p - 1;
|
---|
79 |
|
---|
80 | swap(ary, pivotIndex, r);
|
---|
81 | var pivot = ary[r];
|
---|
82 |
|
---|
83 | // Immediately after `j` is incremented in this loop, the following hold
|
---|
84 | // true:
|
---|
85 | //
|
---|
86 | // * Every element in `ary[p .. i]` is less than or equal to the pivot.
|
---|
87 | //
|
---|
88 | // * Every element in `ary[i+1 .. j-1]` is greater than the pivot.
|
---|
89 | for (var j = p; j < r; j++) {
|
---|
90 | if (comparator(ary[j], pivot, false) <= 0) {
|
---|
91 | i += 1;
|
---|
92 | swap(ary, i, j);
|
---|
93 | }
|
---|
94 | }
|
---|
95 |
|
---|
96 | swap(ary, i + 1, j);
|
---|
97 | var q = i + 1;
|
---|
98 |
|
---|
99 | // (2) Recurse on each half.
|
---|
100 |
|
---|
101 | doQuickSort(ary, comparator, p, q - 1);
|
---|
102 | doQuickSort(ary, comparator, q + 1, r);
|
---|
103 | }
|
---|
104 | }
|
---|
105 |
|
---|
106 | return doQuickSort;
|
---|
107 | }
|
---|
108 |
|
---|
109 | function cloneSort(comparator) {
|
---|
110 | let template = SortTemplate.toString();
|
---|
111 | let templateFn = new Function(`return ${template}`)();
|
---|
112 | return templateFn(comparator);
|
---|
113 | }
|
---|
114 |
|
---|
115 | /**
|
---|
116 | * Sort the given array in-place with the given comparator function.
|
---|
117 | *
|
---|
118 | * @param {Array} ary
|
---|
119 | * An array to sort.
|
---|
120 | * @param {function} comparator
|
---|
121 | * Function to use to compare two items.
|
---|
122 | */
|
---|
123 |
|
---|
124 | let sortCache = new WeakMap();
|
---|
125 | exports.quickSort = function (ary, comparator, start = 0) {
|
---|
126 | let doQuickSort = sortCache.get(comparator);
|
---|
127 | if (doQuickSort === void 0) {
|
---|
128 | doQuickSort = cloneSort(comparator);
|
---|
129 | sortCache.set(comparator, doQuickSort);
|
---|
130 | }
|
---|
131 | doQuickSort(ary, comparator, start, ary.length - 1);
|
---|
132 | };
|
---|