Reputation: 2807
According to Mozilla documentation :
The time and space complexity of the sort cannot be guaranteed as it depends on the implementation.
Is it at least safe to assume that it's not O(n^2)
? Is there anywhere more detailed data about how it's implemented ? Thanks.
Upvotes: 71
Views: 78742
Reputation: 3719
Theory and practice: In theory there is no difference between theory and practice, but in practice there is.
- Theory: everything is clear, but nothing works;
- Practice: everything works, but nothing is clear;
- Sometimes theory meets practice: nothing works and nothing is clear.
Big O notation is great for assessing the scalability of an algorithm, but does not provide a means of direct comparison of performance between implementations of an algorithm...
Case in point is the implementation of Array.sort() within browsers. Despite Timsort having a better Big O profile than Merge sort (see https://www.bigocheatsheet.com/), empirical testing shows that the implementation of Timsort in Chrome's V8 engine is clearly outperformed on average by the implementation of Merge sort in Firefox.
The charts below each show two sets of data points:
Furthermore, Big O notation provides a general rule of thumb to expect from the scalability of the algorithm, but does not address the variability. The Chrome V8 implementation of the Timsort algorithm has a wider variability in its execution than the Firefox Merge sort, and despite Timsort's better Big O profile, even Timsort's best times are not better than the Merge sort's worst times. At the risk of starting a religious war, this does not mean that the Timsort is worse than Merge sort, as this could simply be a case of better overall performance by Firefox's implementation of JavaScript.
The data for the charts above was generated from the following code on my Acer Aspire E5-5775G Signature Edition having an Intel Core i5-7200U CPU @2.50GHz and 8GB of RAM. The data was then imported into Excel, analyzed for the 95% bounding range, and then charted. The axes scales on the charts are normalized for ease of visual comparison.
function generateDataPoints( qtyOfTests, arrayRange, valueRange, nearlySortedChange ) {
let loadingTheArray = [];
let randomSortMetrics = [];
let nearlySortedMetrics = [];
for ( let testNo = 0; testNo < qtyOfTests; testNo++ ) {
if ( testNo % 10 === 0 ) console.log( testNo );
// Random determine the size of the array given the range, and then
// randomly fill the array with values.
let testArray = [];
let testArrayLen = Math.round( Math.random() * ( arrayRange.hi - arrayRange.lo ) ) + arrayRange.lo;
start = performance.now();
for ( let v = 0; v < testArrayLen; v++ ) {
testArray[ v ] = Math.round( Math.random() * ( valueRange.hi - valueRange.lo ) ) + valueRange.lo;
}
end = performance.now();
loadingTheArray[ testNo ] = { x: testArrayLen, y: Math.floor( end - start ) };
// Perform the sort and capture the result.
start = performance.now();
testArray.sort( (a, b ) => a - b );
end = performance.now();
randomSortMetrics[ testNo ] = { x: testArrayLen, y: Math.floor( end - start ) };
// Now, let's change a portion of the sorted values and sort again.
let qtyOfValuesToChange = testArrayLen * nearlySortedChange;
for ( let i = 0; i < qtyOfValuesToChange; i++ ) {
let v = Math.round( Math.random() * testArrayLen );
testArray[ v ] = Math.round( Math.random() * ( valueRange.hi - valueRange.lo ) ) + valueRange.lo;
}
start = performance.now();
testArray.sort( (a, b ) => a - b );
end = performance.now();
nearlySortedMetrics[ testNo ] = { x: testArrayLen, y: Math.floor( end - start ) };
}
return [ loadingTheArray, randomSortMetrics, nearlySortedMetrics ];
}
// Let's start running tests!
let arraySizeRange = { lo: 100, hi: 500000 };
let valueRange = { lo: 0, hi: 2 ** 32 - 1 };
let results = generateDataPoints( 500, arraySizeRange, valueRange, 0.02 );
let tabFormat = 'No Of Elements\tTime to Load Array\tFull Sort\tMostly Sorted\n';
for ( let i = 0; i < results[0].length; i++ ) {
tabFormat += `${results[0][i].x}\t${results[0][i].y}\t${results[1][i].y}\t${results[2][i].y}\n`;
}
console.log( tabFormat );
The takeaway is that the performance of an algorithm, ostensibly being better based on Big O notation, has many factors that drive its overall performance, and a better Big O does not necessarily translate to better performance...
Upvotes: 43
Reputation: 4115
It depends on the browser/engine.
Since V8 v7.0 and Chrome 70 Timsort algorithm is used.
Which, for smaller arrays, has a time complexity of O(n) and space complexity of 0(1).
And for larger arrays, it has a time complexity of O(nlog(n)) and space complexity of O(n).
The older version of V8 uses Quick Sort, and it has a check for small arrays (up to 10 elements).
So for smaller arrays, the time complexity is O(n^2), and space complexity is O(1).
For larger arrays, time complexity is Θ(n log(n)) (average case), and space complexity is O(log(n))
Upvotes: 7
Reputation: 370759
Firefox uses merge sort. Chrome, as of version 70, uses a hybrid of merge sort and insertion sort called Timsort.
The time complexity of merge sort is O(n log n)
. While the specification does not specify the sorting algorithm to use, in any serious environment, you can probably expect that sorting larger arrays does not take longer than O(n log n)
(because if it did, it would be easy to change to a much faster algorithm like merge sort, or some other log-linear method).
While comparison sorts like merge sort have a lower bound of O(n log n)
(i.e. they take at least this long to complete), Timsort takes advantages of "runs" of already ordered data and so has a lower bound of O(n)
.
Upvotes: 119