From 8720cd14e530d6ec45f8a40634a7752cb852cca4 Mon Sep 17 00:00:00 2001 From: Jack O'Connor Date: Fri, 15 Aug 2025 19:11:45 +0100 Subject: [PATCH 1/6] POC: Use Rust's `sort_by` instead of `timsort::try_sort_by_gt` MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Time improvements **rust-timsort** (Timing from Linux laptop - I didn't repeat as 16 mins long) ```sh ✦ ❯ time cargo run --release -- -c "from random import random; sorted([random() for i in range(1_000_000)]); print('DONE');" Finished `release` profile [optimized] target(s) in 0.16s Running `target/release/rustpython -c 'from random import random; sorted([random() for i in range(1_000_000)]); print('\''DONE'\'');'` DONE real 16m52.217s user 16m51.926s sys 0m0.174s ``` **Built-in** (Timing from MacOS laptop) ```sh ❯ time cargo run --release -- -c "from random import random; sorted([random() for i in range(1_000_000)]); print('DONE');" Finished `release` profile [optimized] target(s) in 0.89s Running `target/release/rustpython -c 'from random import random; sorted([random() for i in range(1_000_000)]); print('\''DONE'\'');'` DONE cargo run --release -- -c 0.96s user 0.23s system 54% cpu 2.196 total ``` ## Verify list is sorted ```sh ❯ cargo run --release -- -c "from random import random; l = sorted([random() for i in range(1_000_000)]); print(all(l[i] <= l[i+1] for i in range(len(l) - 1)) ─╯ );" TRUE ``` ## TODO - This uses `.unwrap` because I don't know how to handle the error case - This doesn't use Ordering::Equal so it needs to either be confirmed that this is a stable sorting algorithm, or else make it stable --- vm/src/builtins/list.rs | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/vm/src/builtins/list.rs b/vm/src/builtins/list.rs index 9a7b589418..dd7b55af1c 100644 --- a/vm/src/builtins/list.rs +++ b/vm/src/builtins/list.rs @@ -504,6 +504,7 @@ impl Representable for PyList { } } +use std::cmp::Ordering; fn do_sort( vm: &VirtualMachine, values: &mut Vec, @@ -515,17 +516,26 @@ fn do_sort( } else { PyComparisonOp::Gt }; - let cmp = |a: &PyObjectRef, b: &PyObjectRef| a.rich_compare_bool(b, op, vm); + let cmp = |a: &PyObjectRef, b: &PyObjectRef| { + let res = a.rich_compare_bool(b, op, vm).unwrap(); + if res { + Ordering::Greater + } else { + Ordering::Less + } + }; if let Some(ref key_func) = key_func { let mut items = values .iter() .map(|x| Ok((x.clone(), key_func.call((x.clone(),), vm)?))) .collect::, _>>()?; - timsort::try_sort_by_gt(&mut items, |a, b| cmp(&a.1, &b.1))?; + // timsort::try_sort_by_gt(&mut items, |a, b| cmp(&a.1, &b.1))?; + items.sort_by(|a, b| cmp(&a.1, &b.1)); *values = items.into_iter().map(|(val, _)| val).collect(); } else { - timsort::try_sort_by_gt(values, cmp)?; + // timsort::try_sort_by_gt(values, cmp)?; + values.sort_by(|a, b| cmp(a, b)); } Ok(()) From 8b909c36c2c228eba2cae91c85a7c61e616947bc Mon Sep 17 00:00:00 2001 From: Jack O'Connor Date: Sat, 16 Aug 2025 17:40:31 +0100 Subject: [PATCH 2/6] POC: Return Ordering::Equal if exception and see what happens --- vm/src/builtins/list.rs | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/vm/src/builtins/list.rs b/vm/src/builtins/list.rs index dd7b55af1c..738ded2a31 100644 --- a/vm/src/builtins/list.rs +++ b/vm/src/builtins/list.rs @@ -517,11 +517,9 @@ fn do_sort( PyComparisonOp::Gt }; let cmp = |a: &PyObjectRef, b: &PyObjectRef| { - let res = a.rich_compare_bool(b, op, vm).unwrap(); - if res { - Ordering::Greater - } else { - Ordering::Less + match a.rich_compare_bool(b, op, vm) { + Ok(res) => if res { Ordering::Greater } else { Ordering::Less }, + Err(_) => Ordering::Equal, } }; From c2ef2e3babe3a36a7ae40c20a74d188b85467a1f Mon Sep 17 00:00:00 2001 From: Jack O'Connor Date: Sun, 17 Aug 2025 20:19:22 +0100 Subject: [PATCH 3/6] POC: Track PyBaseExceptionRef using mut variable Also use sort_unstable_by since we only get Less/Greater from the rich compare bool function anyway. Returning Equal in the error case will allow the sort to not panic. --- vm/src/builtins/list.rs | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/vm/src/builtins/list.rs b/vm/src/builtins/list.rs index 738ded2a31..490ebba840 100644 --- a/vm/src/builtins/list.rs +++ b/vm/src/builtins/list.rs @@ -505,6 +505,7 @@ impl Representable for PyList { } use std::cmp::Ordering; +use crate::exceptions::types::PyBaseExceptionRef; fn do_sort( vm: &VirtualMachine, values: &mut Vec, @@ -516,10 +517,16 @@ fn do_sort( } else { PyComparisonOp::Gt }; - let cmp = |a: &PyObjectRef, b: &PyObjectRef| { + + // If a PyException is encountered stop swapping elements and store the error. + let mut error_slot: Option = None; + let mut cmp = |a: &PyObjectRef, b: &PyObjectRef| { match a.rich_compare_bool(b, op, vm) { Ok(res) => if res { Ordering::Greater } else { Ordering::Less }, - Err(_) => Ordering::Equal, + Err(e) => { + error_slot = Some(e); + Ordering::Equal + }, } }; @@ -529,14 +536,17 @@ fn do_sort( .map(|x| Ok((x.clone(), key_func.call((x.clone(),), vm)?))) .collect::, _>>()?; // timsort::try_sort_by_gt(&mut items, |a, b| cmp(&a.1, &b.1))?; - items.sort_by(|a, b| cmp(&a.1, &b.1)); + items.sort_unstable_by(|a, b| cmp(&a.1, &b.1)); *values = items.into_iter().map(|(val, _)| val).collect(); } else { - // timsort::try_sort_by_gt(values, cmp)?; - values.sort_by(|a, b| cmp(a, b)); + values.sort_unstable_by(|a, b| cmp(a, b)); } - Ok(()) + // After the sort is done, check if an error was captured. + match error_slot { + Some(e) => Err(e), + None => Ok(()), + } } #[pyclass(module = false, name = "list_iterator", traverse)] From 5f9a9fdc3edfa2dbbc32fe605cd916a1aa832eaa Mon Sep 17 00:00:00 2001 From: Jack O'Connor Date: Sun, 17 Aug 2025 20:20:44 +0100 Subject: [PATCH 4/6] POC: cargo fmt --- vm/src/builtins/list.rs | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/vm/src/builtins/list.rs b/vm/src/builtins/list.rs index 490ebba840..85d042dc7b 100644 --- a/vm/src/builtins/list.rs +++ b/vm/src/builtins/list.rs @@ -504,8 +504,8 @@ impl Representable for PyList { } } -use std::cmp::Ordering; use crate::exceptions::types::PyBaseExceptionRef; +use std::cmp::Ordering; fn do_sort( vm: &VirtualMachine, values: &mut Vec, @@ -520,13 +520,17 @@ fn do_sort( // If a PyException is encountered stop swapping elements and store the error. let mut error_slot: Option = None; - let mut cmp = |a: &PyObjectRef, b: &PyObjectRef| { - match a.rich_compare_bool(b, op, vm) { - Ok(res) => if res { Ordering::Greater } else { Ordering::Less }, - Err(e) => { - error_slot = Some(e); - Ordering::Equal - }, + let mut cmp = |a: &PyObjectRef, b: &PyObjectRef| match a.rich_compare_bool(b, op, vm) { + Ok(res) => { + if res { + Ordering::Greater + } else { + Ordering::Less + } + } + Err(e) => { + error_slot = Some(e); + Ordering::Equal } }; From 9b3871d3603eea28744df81c2e84aef3d064e8f1 Mon Sep 17 00:00:00 2001 From: Jack O'Connor Date: Sun, 17 Aug 2025 20:26:25 +0100 Subject: [PATCH 5/6] POC: cargo clippy --- vm/src/builtins/list.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vm/src/builtins/list.rs b/vm/src/builtins/list.rs index 85d042dc7b..97e60ef052 100644 --- a/vm/src/builtins/list.rs +++ b/vm/src/builtins/list.rs @@ -543,7 +543,7 @@ fn do_sort( items.sort_unstable_by(|a, b| cmp(&a.1, &b.1)); *values = items.into_iter().map(|(val, _)| val).collect(); } else { - values.sort_unstable_by(|a, b| cmp(a, b)); + values.sort_unstable_by(cmp(a, b)); } // After the sort is done, check if an error was captured. From 78d10eb351edca2074d6c423728b860ebca51aa7 Mon Sep 17 00:00:00 2001 From: Jack O'Connor Date: Sun, 17 Aug 2025 20:29:52 +0100 Subject: [PATCH 6/6] more clippy --- vm/src/builtins/list.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vm/src/builtins/list.rs b/vm/src/builtins/list.rs index 97e60ef052..30379dc865 100644 --- a/vm/src/builtins/list.rs +++ b/vm/src/builtins/list.rs @@ -543,7 +543,7 @@ fn do_sort( items.sort_unstable_by(|a, b| cmp(&a.1, &b.1)); *values = items.into_iter().map(|(val, _)| val).collect(); } else { - values.sort_unstable_by(cmp(a, b)); + values.sort_unstable_by(cmp); } // After the sort is done, check if an error was captured.