Skip to content

Commit cd9b986

Browse files
committed
[JSC] Implement galloping merge in PowerSort
https://bugs.webkit.org/show_bug.cgi?id=310948 rdar://17190833 Reviewed by Yijia Huang. This patch enhances our existing PowerSort implementation with three extensions. 1. We are always returning src as a result. And dst side is just a working copy for the algorithm. We avoid unnecessary initial copy. 2. We extended `extendRunRight` to `extendAndNormalizeRun`, which can also detect descending patterns and extend the run by reversing at last. 3. We introduced galloping merge: this is originally introduced in TimSort[1]. The idea of galloping merge is "it is quite common that cluster of elements are lower / higher than the first / last element of the other run. Then instead of comparing both first elements one-by-one and merging them, we should find the lower / higher elements than the other run's first / last element, and copy them in a bulk manner". This can avoid calling comparison invocation frequently, which is very costly in JS sort implementation. gallopLeft and gallopRight is basically finding an insertion point in the run, but instead of using binary search, it is using galloping. ToT Patched magic-forest 464.3367+-5.3030 ^ 346.2807+-1.6506 ^ definitely 1.3409x faster Apple OSS approval: OSS-24108 [1]: https://github.com/python/cpython/blob/main/Objects/listsort.txt Test: JSTests/stress/sort-galloping-merge-optimizations.js * JSTests/slowMicrobenchmarks/magic-forest.js: Added. (Forest): (Forest.prototype.wolfDevoursGoat): (Forest.prototype.lionDevoursGoat): (Forest.prototype.lionDevoursWolf): (Forest.prototype.meal): (Forest.prototype.isStable): (Forest.prototype.toString): (Forest.compare): (meal): (stableForests): (findStableForests): (go): (goUI): * JSTests/stress/sort-galloping-merge-optimizations.js: Added. (assertSorted): (assertStable): (assertArraysEqual): (assertSameElements): (makePRNG): (throw.new.Error): * Source/JavaScriptCore/runtime/ArrayPrototype.cpp: (JSC::sortStableSort): (JSC::sortImpl): * Source/JavaScriptCore/runtime/JSGenericTypedArrayViewPrototypeFunctions.h: (JSC::genericTypedArrayViewProtoFuncSortImpl): * Source/JavaScriptCore/runtime/StableSort.h: (JSC::extendAndNormalizeRun): (JSC::gallopLeft): (JSC::gallopRight): (JSC::mergePowersortRuns): (JSC::arrayStableSort): Canonical link: https://commits.webkit.org/310282@main
1 parent 67d9316 commit cd9b986

5 files changed

Lines changed: 1499 additions & 60 deletions

File tree

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
// See http://unriskinsight.blogspot.com/2014/06/fast-functional-goats-lions-and-wolves.html
2+
// Sascha Kratky (kratky@unrisk.com), uni software plus GmbH & MathConsult GmbH
3+
//
4+
// run with Google v8:
5+
// d8 magicForest.js -- 117 155 106
6+
//
7+
// run with Mozilla SpiderMonkey:
8+
// js magicForest.js 117 155 106
9+
10+
function Forest(goats, wolves, lions) {
11+
this.goats = goats;
12+
this.wolves = wolves;
13+
this.lions = lions;
14+
}
15+
16+
Forest.prototype.wolfDevoursGoat = function() {
17+
if (this.goats > 0 && this.wolves > 0)
18+
return new Forest(this.goats - 1, this.wolves - 1, this.lions + 1);
19+
else
20+
return null;
21+
}
22+
23+
Forest.prototype.lionDevoursGoat = function() {
24+
if (this.goats > 0 && this.lions > 0)
25+
return new Forest(this.goats - 1, this.wolves + 1, this.lions - 1);
26+
else
27+
return null;
28+
}
29+
30+
Forest.prototype.lionDevoursWolf = function() {
31+
if (this.lions > 0 && this.wolves > 0)
32+
return new Forest(this.goats + 1, this.wolves - 1, this.lions - 1);
33+
else
34+
return null;
35+
}
36+
37+
Forest.prototype.meal = function() {
38+
return [this.wolfDevoursGoat(), this.lionDevoursGoat(), this.lionDevoursWolf()].filter(
39+
function(f) { return f !== null; })
40+
}
41+
42+
Forest.prototype.isStable = function() {
43+
if (this.goats === 0) return (this.wolves === 0) || (this.lions === 0);
44+
return (this.wolves === 0) && (this.lions === 0);
45+
}
46+
47+
Forest.prototype.toString = function()
48+
{
49+
return "[goats=" + this.goats + ", wolves=" + this.wolves + ", lions=" + this.lions + "]";
50+
}
51+
52+
Forest.compare = function(forest1, forest2) {
53+
var cmp = forest1.goats-forest2.goats;
54+
if (cmp !== 0) return cmp;
55+
cmp = forest1.wolves-forest2.wolves;
56+
if (cmp !== 0) return cmp;
57+
return forest1.lions-forest2.lions;
58+
}
59+
60+
function meal(forests) {
61+
var nextForests = [];
62+
forests.forEach(function(forest) {
63+
nextForests.push.apply(nextForests, forest.meal())
64+
});
65+
// remove duplicates
66+
return nextForests.sort(Forest.compare).filter(function(forest, index, array) {
67+
return (index === 0 || Forest.compare(forest, array[index - 1]) !== 0);
68+
});
69+
}
70+
71+
function devouringPossible(forests) {
72+
return forests.length > 0 && !forests.some(function(f) { return f.isStable(); });
73+
}
74+
75+
function stableForests(forests) {
76+
return forests.filter(function(f) { return f.isStable(); });
77+
}
78+
79+
function findStableForests(forest) {
80+
var forests = [forest];
81+
while (devouringPossible(forests)) {
82+
forests = meal(forests);
83+
}
84+
return stableForests(forests);
85+
}
86+
87+
function go(g,w,l) {
88+
var initialForest = new Forest(g, w, l);
89+
findStableForests(initialForest)/*.forEach(function(f){ console.log(f); })*/
90+
}
91+
92+
function goUI()
93+
{
94+
d=new Date();
95+
g=parseInt("217");
96+
w=parseInt("255");
97+
l=parseInt("206");
98+
go(g, w, l);
99+
// print("Time taken: " + (new Date() - d) + "ms");
100+
}
101+
goUI();

0 commit comments

Comments
 (0)