WebKit sorts JS arrays using Selection Sort
trac.webkit.org
trac.webkit.org
631 if (thisObj->classInfo() == &JSArray::s_info && !asArray(thisObj)->inSparseMode()) {
that only non-arrays, or arrays in some kind of "sparse" mode are sorted inefficiently.I'm not sure what sparse mode is, but let's try my Raymond Chen inspired psychic powers: if you assign a[0]=1 and a[1000000]=2, you don't want a 1 to 999999 to be stored, so an array like that will end up in a sparse mode which functions more like a hash table keyed by integers.
The same applies to non-arrays: since they aren't true arrays, they won't be stored in the blessed, contiguous way that the sort routine is expecting, so a slower access method has to be used.
Now, fast sorting is presumably written assuming the array is in a contiguous array. Since in sparse mode the array isn't like that, we aren't on the fast path, so it doesn't matter if we are slow, and correctness and special cases are more important, hence the simple selection sort algorithm.
Note also that line 633, 635 and 637 further specialise the fast path into three cases: a default sort with no user-defined comparator, a sort based on a built-in numerical comparator, and a more general sort using a user-defined comparator. (EDIT: The functions called on 633, 635 and 637 are defined in http://trac.webkit.org/browser/trunk/Source/JavaScriptCore/r... from line 1409 onwards, and use quicksort or mergesort, except in the general case which uses an AVL tree, which feels odd but I'm sure there was a reason.)
TL;DR: This only applies to certain arrays; the performance is generally fine else someone would have noticed by now; if you are sorting array-like things or sparse arrays regularly, profile to discover if this is a problem for you.
// For numeric comparison, which is fast, qsort is faster than mergesort. We
// also don't require mergesort's stability, since there's no user visible
// side-effect from swapping the order of equal primitive values.I am not arguing selection sort is a good sort for the reason chosen, but I will maintain that the real (yes: "real", that is the correct term to use here) sorting algorithm used by WebKit for "JS arrays" (remember: I was complaining about "how incorrect this submission is") is actually a choice between mergesort, quicksort, and an AVL tree.
Now, if the submission title had been "WebKit sorts sparse JS arrays using selection sort" or even "WebKit sorts non-standard JS arrays using selection sort" (to evoke the wording of the commit message 7 months ago), that would be different: that would not be a linkbait title. I could even maybe get behind "WebKit uses four different sorting algorithms, and one of them is selection sort?!".
However, when one sees the title "WebKit sorts JS arrays using Selection Sort", I think many, if not most, reasonable developers go "oh, wow, maybe I should be avoiding the WebKit sort algorithm, given that my arrays are reasonably sized... I wonder if they'll get that fixed"; but, when you click through to the actual code and read it you realize this would be wrong.
So, from my perspective, you are now arguing a strawman, and are doing so fairly abrasively :(. To be clear: I certainly am not defending the existence of selection sort in the codebase, even for non-standard arrays. That does not make this submission correct, or even reasonable: it is looking at the wrong code and making it out to be something it is not (even quite generally, "important").
Regardless, I got curious, and decided to look more into that sorting code. It is apparently sufficiently old that it predates WebKit being called "WebKit", so I went back through kdelibs and found the original commit that added it, from January 2001. At this time, most of the array implementation was still "does nothing, needs to be implemented" or "does the wrong thing, needs to be fixed".
http://quickgit.kde.org/index.php?p=kdelibs.git&a=commit...
It was in March of 2003, during a commit-spree of merging from Safari back to kjs, that the quick sort implementation and code to use it for JS arrays was added. This means that this submission's title was correct for two years, incorrect for almost 9 years, and then maybe-sort-of-almost-correct for 7 months.
Chrome uses quicksort + insertion sort, as mentioned in another comment. Firefox uses merge sort + insertion sort (https://mxr.mozilla.org/mozilla-central/source/js/src/jsarra...).
As a side note, Firefox's sort implementation slows down drastically when you use a custom comparison function because it has to call from C++ back out to JS (https://bugzilla.mozilla.org/show_bug.cgi?id=715181). Chrome's sort is presumably implemented in JS for this reason.
EDIT: Sparse mode is when JSC::JSArray switches from storing the array data as a vector to storing it in a map. You can see from the code at http://trac.webkit.org/browser/trunk/Source/JavaScriptCore/r... that a vector will be used for sufficiently small arrays (< 10,000 elements) and for larger arrays that are at least 12.5% full.
No, O(N^2) sorting algorithms are just dumb in library/runtime[1] code. No one expects that, it will eventually bite anyone who uses the code. And this code is a booby trap, no one even knows if they're using it!
[1] In application code, where you might know a priori that they'll never sort more than "one screen worth" of data, I can see it being a reasonable hack. But never as part of a standard feature called "sort".
author kocienda <kocienda@>
Fri, 24 Aug 2001 10:24:45 -0400 (14:24 +0000)
Imported sources from kde-2.2 distribution
http://git.chromium.org/gitweb/?p=external/Webkit.git;a=blob... var a=new Array(4e9);
for (var i=1; i<a.length; i=Math.ceil(i*1.0001))
a[i]=new Number(i).toString();
a.sort();
The speed suggest that this sparse string array is not sorted with a quadratic algorithm (today).EDIT: In order to go down the fallback path the array needs to be both sufficiently large and sufficiently sparse (< 12.5% occupied). You can see the difference in algorithms by modifying your test case like so:
Dense, fast path:
var a = [];
for (var i = 1; i < 4e6; i++)
a[i * 5] = i.toString();
a.sort();
Sparse, fallback path: var a = [];
for (var i = 1; i < 4e6; i++)
a[i * 10] = i.toString();
a.sort();See this animated example that danso posted: http://www.sorting-algorithms.com/
Yes, a difference of maybe 5ms won't be noticed... unless you're going a lot of sorting. Selection Sort is basically the slowest sort of all.
That piece of code you think must be slow and must be impacting the application speed? It might not be consequential at all. Spending 4 hours optimizing it might get you exactly 0% improvement.
And that one line you think is benign and would take 5 minutes to opt might end up being the key to speeding up your entire algorithm tenfold.
It's important to understand speed and complexity when architecting software, but it's just as important to know how it really performs in the real world, on the CPU, with real memory constraints and real data.
I'm not saying this is true at all for this particular algorithm, but it's been uncannily true for me in the past and I still fool myself to this day trying to optimise prematurely based on my primitive human computer.
http://code.google.com/p/v8/source/browse/trunk/src/array.js...
Afaik v8 (and Java's pre Java 7 versions) quicksort algorithms are based on Jon Bentley's work.
(That's the technical reason; if you're wearing a tinfoil hat, you could argue that Apple wants to make sure native apps work a lot better than thin UIWebView shims, for lock-in and whatnot.)
Fun side-note: you can use a matrix diagram to visualize how your browser sorts, letting you determine your browser's sort algorithm visually. For example, Chrome uses median-of-3 quicksort, and Firefox uses mergesort. See for yourself!
http://bost.ocks.org/mike/shuffle/compare.html
http://www.flickr.com/photos/mbostock/sets/72157628971703067...
EDIT: One interesting case is that of sparse arrays (e.g., the arrays which trigger JavaScriptCore to fall down the slow path that prompted this discussion). It seems that your JavaScript quicksort implementation doesn't handle these correctly: it gives different results from Array.prototype.sort and is dramatically slower as well. I'm guessing the performance impact comes from the fact that from JavaScript's point of view the holes in the array are identical undefined values. I'm not sure why correctness would be affected though.
Oh, OK. Glad you could save yourself some time/LOC.