Skip to content

Commit fc58bdf

Browse files
committed
Specialize tuple sorts on their first elements
1 parent 3010dd4 commit fc58bdf

2 files changed

Lines changed: 120 additions & 14 deletions

File tree

crates/vm/src/builtins/list.rs

Lines changed: 115 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ use crate::common::lock::{
99
use crate::object::{Traverse, TraverseFn};
1010
use crate::{
1111
AsObject, Context, Py, PyObject, PyObjectRef, PyPayload, PyRef, PyResult,
12-
builtins::{PyFloat, PyInt, PyStr},
12+
builtins::{PyFloat, PyInt, PyStr, PyTuple},
1313
class::PyClassImpl,
1414
convert::ToPyObject,
1515
function::{ArgSize, Either, FuncArgs, OptionalArg, PyComparisonValue},
@@ -635,38 +635,100 @@ impl Representable for PyList {
635635
}
636636
}
637637

638+
enum Elem {
639+
Str,
640+
Int,
641+
Float,
642+
Object(RichCompareFunc),
643+
Generic,
644+
}
645+
638646
enum PreSort {
639647
Str,
640648
Int,
641649
Float,
642650
Object(RichCompareFunc),
651+
Tuple(Elem),
643652
Generic,
644653
}
645654

655+
impl From<Elem> for PreSort {
656+
fn from(e: Elem) -> Self {
657+
match e {
658+
Elem::Str => Self::Str,
659+
Elem::Int => Self::Int,
660+
Elem::Float => Self::Float,
661+
Elem::Object(f) => Self::Object(f),
662+
Elem::Generic => Self::Generic,
663+
}
664+
}
665+
}
666+
667+
fn classify(class: &Py<PyType>, vm: &VirtualMachine) -> Elem {
668+
if class.is(vm.ctx.types.str_type) {
669+
Elem::Str
670+
} else if class.is(vm.ctx.types.int_type) {
671+
Elem::Int
672+
} else if class.is(vm.ctx.types.float_type) {
673+
Elem::Float
674+
} else if let Some(f) = class.slots.richcompare.load() {
675+
Elem::Object(f)
676+
} else {
677+
Elem::Generic
678+
}
679+
}
680+
646681
fn pre_sort_check<'a>(
647682
mut keys: impl Iterator<Item = &'a PyObjectRef>,
648683
vm: &VirtualMachine,
649684
) -> PreSort {
650685
let Some(first) = keys.next() else {
651686
return PreSort::Generic;
652687
};
653-
let class = first.class();
654-
if !keys.all(|o| o.class().is(class)) {
655-
return PreSort::Generic;
656-
}
657-
if class.is(vm.ctx.types.str_type) {
658-
PreSort::Str
659-
} else if class.is(vm.ctx.types.int_type) {
660-
PreSort::Int
661-
} else if class.is(vm.ctx.types.float_type) {
662-
PreSort::Float
663-
} else if let Some(f) = class.slots.richcompare.load() {
664-
PreSort::Object(f)
688+
689+
if let Some(t) = first
690+
.downcast_ref_if_exact::<PyTuple>(vm)
691+
.filter(|t| !t.as_slice().is_empty())
692+
{
693+
pre_sort_check_tuples(&t.as_slice()[0], keys, vm)
665694
} else {
666-
PreSort::Generic
695+
let class = first.class();
696+
if keys.all(|k| k.class().is(class)) {
697+
classify(class, vm).into()
698+
} else {
699+
PreSort::Generic
700+
}
667701
}
668702
}
669703

704+
fn pre_sort_check_tuples<'a>(
705+
first_elem: &PyObjectRef,
706+
keys: impl Iterator<Item = &'a PyObjectRef>,
707+
vm: &VirtualMachine,
708+
) -> PreSort {
709+
let class = first_elem.class();
710+
let mut all_same_type = true;
711+
712+
for k in keys {
713+
let Some(t) = k
714+
.downcast_ref_if_exact::<PyTuple>(vm)
715+
.filter(|t| !t.as_slice().is_empty())
716+
else {
717+
return PreSort::Generic;
718+
};
719+
if all_same_type && !t.as_slice()[0].class().is(class) {
720+
all_same_type = false;
721+
}
722+
}
723+
724+
let elem = if !all_same_type || class.is(vm.ctx.types.tuple_type) {
725+
Elem::Generic
726+
} else {
727+
classify(class, vm)
728+
};
729+
PreSort::Tuple(elem)
730+
}
731+
670732
fn str_lt(a: &PyObjectRef, b: &PyObjectRef) -> bool {
671733
a.downcast_ref::<PyStr>().unwrap().as_bytes() < b.downcast_ref::<PyStr>().unwrap().as_bytes()
672734
}
@@ -704,6 +766,37 @@ fn object_lt(
704766
}
705767
}
706768

769+
fn elem_lt(elem: &Elem, a: &PyObjectRef, b: &PyObjectRef, vm: &VirtualMachine) -> PyResult<bool> {
770+
match elem {
771+
Elem::Str => Ok(str_lt(a, b)),
772+
Elem::Int => Ok(int_lt(a, b)),
773+
Elem::Float => Ok(float_lt(a, b)),
774+
Elem::Object(f) => object_lt(*f, a, b, vm),
775+
Elem::Generic => a.rich_compare_bool(b, PyComparisonOp::Lt, vm),
776+
}
777+
}
778+
779+
fn tuple_lt(elem: &Elem, a: &PyObjectRef, b: &PyObjectRef, vm: &VirtualMachine) -> PyResult<bool> {
780+
let a = a.downcast_ref::<PyTuple>().unwrap().as_slice();
781+
let b = b.downcast_ref::<PyTuple>().unwrap().as_slice();
782+
783+
let mut i = 0;
784+
while i < a.len() && i < b.len() {
785+
if !a[i].rich_compare_bool(&b[i], PyComparisonOp::Eq, vm)? {
786+
break;
787+
}
788+
i += 1;
789+
}
790+
if i >= a.len() || i >= b.len() {
791+
return Ok(a.len() < b.len());
792+
}
793+
if i == 0 {
794+
elem_lt(elem, &a[0], &b[0], vm)
795+
} else {
796+
a[i].rich_compare_bool(&b[i], PyComparisonOp::Lt, vm)
797+
}
798+
}
799+
707800
fn timsort_specialized<T, K>(
708801
vm: &VirtualMachine,
709802
items: &mut [T],
@@ -747,6 +840,14 @@ where
747840
};
748841
object_lt(cmp, a, b, vm)
749842
}),
843+
PreSort::Tuple(elem) => timsort(items, &mut |a, b| {
844+
let (a, b) = if reverse {
845+
(key(b), key(a))
846+
} else {
847+
(key(a), key(b))
848+
};
849+
tuple_lt(&elem, a, b, vm)
850+
}),
750851
PreSort::Generic => timsort(items, &mut |a, b| {
751852
let (a, b) = if reverse {
752853
(key(b), key(a))

extra_tests/snippets/builtin_list.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -257,6 +257,11 @@ class IntSub(int):
257257
nan = float("nan")
258258
assert repr(sorted([nan, 1.0, 2.0])) == "[nan, 1.0, 2.0]"
259259
assert sorted([b"b", b"a", b"c"]) == [b"a", b"b", b"c"]
260+
assert sorted([(2, 9), (1, 5), (2, 1)]) == [(1, 5), (2, 1), (2, 9)]
261+
assert sorted([(1, "b"), (1, "a")]) == [(1, "a"), (1, "b")]
262+
assert sorted([(1,), (1, 2), ()]) == [(), (1,), (1, 2)]
263+
assert sorted([((2,), "x"), ((1,), "y")]) == [((1,), "y"), ((2,), "x")]
264+
assert sorted([(1, "a"), (2.5, "b"), (0, "c")]) == [(0, "c"), (1, "a"), (2.5, "b")]
260265

261266
lst = [3, 1, 5, 2, 4]
262267

0 commit comments

Comments
 (0)