| /* -*- Mode: js; js-indent-level: 2; -*- */ |
| /* |
| * Copyright 2011 Mozilla Foundation and contributors |
| * Licensed under the New BSD license. See LICENSE or: |
| * http://opensource.org/licenses/BSD-3-Clause |
| */ |
| |
| // It turns out that some (most?) JavaScript engines don't self-host |
| // `Array.prototype.sort`. This makes sense because C++ will likely remain |
| // faster than JS when doing raw CPU-intensive sorting. However, when using a |
| // custom comparator function, calling back and forth between the VM's C++ and |
| // JIT'd JS is rather slow *and* loses JIT type information, resulting in |
| // worse generated code for the comparator function than would be optimal. In |
| // fact, when sorting with a comparator, these costs outweigh the benefits of |
| // sorting in C++. By using our own JS-implemented Quick Sort (below), we get |
| // a ~3500ms mean speed-up in `bench/bench.html`. |
| |
| /** |
| * Swap the elements indexed by `x` and `y` in the array `ary`. |
| * |
| * @param {Array} ary |
| * The array. |
| * @param {Number} x |
| * The index of the first item. |
| * @param {Number} y |
| * The index of the second item. |
| */ |
| function swap(ary, x, y) { |
| var temp = ary[x]; |
| ary[x] = ary[y]; |
| ary[y] = temp; |
| } |
| |
| /** |
| * Returns a random integer within the range `low .. high` inclusive. |
| * |
| * @param {Number} low |
| * The lower bound on the range. |
| * @param {Number} high |
| * The upper bound on the range. |
| */ |
| function randomIntInRange(low, high) { |
| return Math.round(low + (Math.random() * (high - low))); |
| } |
| |
| /** |
| * The Quick Sort algorithm. |
| * |
| * @param {Array} ary |
| * An array to sort. |
| * @param {function} comparator |
| * Function to use to compare two items. |
| * @param {Number} p |
| * Start index of the array |
| * @param {Number} r |
| * End index of the array |
| */ |
| function doQuickSort(ary, comparator, p, r) { |
| // If our lower bound is less than our upper bound, we (1) partition the |
| // array into two pieces and (2) recurse on each half. If it is not, this is |
| // the empty array and our base case. |
| |
| if (p < r) { |
| // (1) Partitioning. |
| // |
| // The partitioning chooses a pivot between `p` and `r` and moves all |
| // elements that are less than or equal to the pivot to the before it, and |
| // all the elements that are greater than it after it. The effect is that |
| // once partition is done, the pivot is in the exact place it will be when |
| // the array is put in sorted order, and it will not need to be moved |
| // again. This runs in O(n) time. |
| |
| // Always choose a random pivot so that an input array which is reverse |
| // sorted does not cause O(n^2) running time. |
| var pivotIndex = randomIntInRange(p, r); |
| var i = p - 1; |
| |
| swap(ary, pivotIndex, r); |
| var pivot = ary[r]; |
| |
| // Immediately after `j` is incremented in this loop, the following hold |
| // true: |
| // |
| // * Every element in `ary[p .. i]` is less than or equal to the pivot. |
| // |
| // * Every element in `ary[i+1 .. j-1]` is greater than the pivot. |
| for (var j = p; j < r; j++) { |
| if (comparator(ary[j], pivot) <= 0) { |
| i += 1; |
| swap(ary, i, j); |
| } |
| } |
| |
| swap(ary, i + 1, j); |
| var q = i + 1; |
| |
| // (2) Recurse on each half. |
| |
| doQuickSort(ary, comparator, p, q - 1); |
| doQuickSort(ary, comparator, q + 1, r); |
| } |
| } |
| |
| /** |
| * Sort the given array in-place with the given comparator function. |
| * |
| * @param {Array} ary |
| * An array to sort. |
| * @param {function} comparator |
| * Function to use to compare two items. |
| */ |
| exports.quickSort = function (ary, comparator) { |
| doQuickSort(ary, comparator, 0, ary.length - 1); |
| }; |