Skip to content

Commit 74242b1

Browse files
rakudramaCommit Queue
authored and
Commit Queue
committed
[js_runtime] Use Array.prototype.sort
Sorting is 2-3x faster for a 1024 element list and simple comparison function. Change-Id: Iecb4dceb7155e430fcc2c0ddef977003d747ab9c Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/315760 Reviewed-by: Mayank Patke <[email protected]> Commit-Queue: Stephen Adams <[email protected]>
1 parent 0a3ca5a commit 74242b1

File tree

2 files changed

+87
-1
lines changed

2 files changed

+87
-1
lines changed

sdk/lib/_internal/js_runtime/lib/interceptors.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import 'dart:_js_helper'
2222
checkNull,
2323
checkNum,
2424
checkString,
25+
convertDartClosureToJS,
2526
defineProperty,
2627
diagnoseIndexError,
2728
getIsolateAffinityTag,

sdk/lib/_internal/js_runtime/lib/js_array.dart

Lines changed: 86 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -579,13 +579,98 @@ class JSArray<E> extends JavaScriptObject implements List<E>, JSIndexable<E> {
579579

580580
void sort([int Function(E, E)? compare]) {
581581
checkMutable('sort');
582-
Sort.sort(this, compare ?? _compareAny);
582+
final len = length;
583+
if (len < 2) return;
584+
compare ??= _compareAny;
585+
if (len == 2) {
586+
final a = this[0];
587+
final b = this[1];
588+
if (compare(a, b) > 0) {
589+
// Hand-coded because the compiler does not understand the array is
590+
// mutable at this point.
591+
JS('', '#[#] = #', this, 0, b); // this[0] = b;
592+
JS('', '#[#] = #', this, 1, a); // this[1] = a;
593+
}
594+
return;
595+
}
596+
597+
// Use JavaScript's sort. This requires some pre- and post- processing.
598+
//
599+
// Arrays have three kinds of element that represent Dart `null`:
600+
//
601+
// - `null` values, which are passed to the compare function.
602+
//
603+
// - `undefined` values, which are not passed to the comparator and follow
604+
// the sorted values in the resulting order.
605+
//
606+
// - Empty slots or holes in the array, which look like `undefined`, are
607+
// also not passed to the comparator, and are moved to the end of the
608+
// array to follow the `undefined` values.
609+
//
610+
// Example
611+
//
612+
// [null, /*empty*/, undefined, 'z', /*empty*/, 'a', undefined].sort()
613+
//
614+
// --->
615+
//
616+
// ['a', null, 'z', undefined, undefined, /*empty*/, /*empty*/]
617+
//
618+
// (null goes between 'a' and 'z' because JavaScript's default comparator
619+
// works on `ToString` of the element).
620+
//
621+
// In order to have the undefined and empty elements behave as `null`, they
622+
// are overwritten with `null` before sorting, and reinstated as an
623+
// `undefined` value after. Since all the `null` values are
624+
// indistinguishable, a count is sufficient.
625+
//
626+
// The reason we bother with reinstating `undefined` values is so that
627+
// sorting does not change the contents of an array that has `undefined`
628+
// values from js-interop. Empty slots are not preserved and become
629+
// non-empty slots holding the `undefined` value (which would happen anyway
630+
// with an assignment like `a[i] = a[j]`.
631+
632+
int undefineds = 0;
633+
// The element type might exclude the possibility of there being `null`s,
634+
// but only in sound null safety mode.
635+
if (JS_GET_FLAG('LEGACY') || null is E) {
636+
for (int i = 0; i < length; i++) {
637+
final E element = JS('', '#[#]', this, i);
638+
if (JS('', '# === void 0', element)) {
639+
// Hand-coded write since `this[i] = null;` is a compile-time error
640+
// due to `E` not being nullable.
641+
JS('', '#[#] = #', this, i, null);
642+
undefineds++;
643+
}
644+
}
645+
}
646+
JS('', '#.sort(#)', this, convertDartClosureToJS(compare, 2));
647+
648+
if (undefineds > 0) _replaceSomeNullsWithUndefined(undefineds);
583649
}
584650

585651
static int _compareAny(a, b) {
586652
return Comparable.compare(a, b);
587653
}
588654

655+
// This is separate function since in many programs sorting an array with
656+
// nulls or undefined values is rare. Keeping the code separate reduces
657+
// potential JIT deoptimizations.
658+
@pragma('dart2js:never-inline')
659+
void _replaceSomeNullsWithUndefined(int count) {
660+
assert(count > 0);
661+
int i = length;
662+
// Scan backwards for `null`s and replace one-by-one. They are not
663+
// necessarily adjacent if the compare function places Dart `null` in the
664+
// same equivalence class as some non-null value.
665+
while (i-- > 0) {
666+
final E element = JS('', '#[#]', this, i);
667+
if (JS('', '# === null', element)) {
668+
JS('', '#[#] = void 0', this, i);
669+
if (--count == 0) break;
670+
}
671+
}
672+
}
673+
589674
void shuffle([Random? random]) {
590675
checkMutable('shuffle');
591676
if (random == null) random = new Random();

0 commit comments

Comments
 (0)