Skip to content

Commit 915ebc9

Browse files
timfennisclaude
andcommitted
Perf: add Value::is_number, avoid O(n) static_type in hot path
static_type() on List/Map/Deque now iterates elements (lub). The vectorization_pairs and as_numeric_tuple helpers called it on container values during every 2-arg dynamic dispatch. Replace with is_number() which is O(1) and never allocates. Add # Performance doc warning to static_type() pointing to is_number as the safe alternative. Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
1 parent ccc4917 commit 915ebc9

2 files changed

Lines changed: 27 additions & 3 deletions

File tree

ndc_vm/src/value/mod.rs

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -203,6 +203,15 @@ impl Value {
203203
}
204204
}
205205

206+
/// Returns the static type of this value.
207+
///
208+
/// # Performance
209+
///
210+
/// **O(n) for `List`, `Map`, and `Deque`** — these variants iterate all
211+
/// elements to compute the element type via `lub`. Avoid calling this in
212+
/// hot paths on container values. Use the dedicated helpers instead:
213+
///
214+
/// - [`Value::is_number`] — O(1) check for numeric types
206215
pub fn static_type(&self) -> StaticType {
207216
match self {
208217
Self::Int(_) => StaticType::Int,
@@ -213,6 +222,21 @@ impl Value {
213222
}
214223
}
215224

225+
/// Returns `true` if this value is a numeric type (Int, Float, Rational, or Complex).
226+
///
227+
/// Prefer this over `self.static_type().is_number()` in hot paths — this is O(1)
228+
/// and never allocates, whereas `static_type()` on containers is O(n).
229+
pub fn is_number(&self) -> bool {
230+
match self {
231+
Self::Int(_) | Self::Float(_) => true,
232+
Self::Object(obj) => matches!(
233+
obj.as_ref(),
234+
Object::BigInt(_) | Object::Rational(_) | Object::Complex(_)
235+
),
236+
_ => false,
237+
}
238+
}
239+
216240
/// Consume this value and produce an iterator over its elements.
217241
///
218242
/// Returns `None` for non-iterable types (`Int`, `Float`, `Bool`, `None`,

ndc_vm/src/vm.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -986,7 +986,7 @@ fn as_numeric_tuple(value: &Value) -> Option<&Vec<Value>> {
986986
let Object::Tuple(elems) = obj.as_ref() else {
987987
return None;
988988
};
989-
if !elems.is_empty() && elems.iter().all(|e| e.static_type().is_number()) {
989+
if !elems.is_empty() && elems.iter().all(|e| e.is_number()) {
990990
Some(elems)
991991
} else {
992992
None
@@ -1003,8 +1003,8 @@ fn as_numeric_tuple(value: &Value) -> Option<&Vec<Value>> {
10031003
fn vectorization_pairs(left: &Value, right: &Value) -> Option<Vec<(Value, Value)>> {
10041004
let left_tuple = as_numeric_tuple(left);
10051005
let right_tuple = as_numeric_tuple(right);
1006-
let left_is_scalar = left.static_type().is_number();
1007-
let right_is_scalar = right.static_type().is_number();
1006+
let left_is_scalar = left.is_number();
1007+
let right_is_scalar = right.is_number();
10081008

10091009
match (left_tuple, right_tuple) {
10101010
(Some(ls), Some(rs)) if ls.len() == rs.len() => {

0 commit comments

Comments
 (0)