Skip to content

Commit 2f241be

Browse files
timsaucerclaude
andcommitted
perf(udf): pointer-identity fast path in PartialEq for Python UDFs
`PartialEq` was acquiring the GIL on every comparison via `Python::attach`, just to call `__eq__` on the stored callable. Most equality checks compare `Arc`-shared clones of the same UDF (expression rewriting, plan diffing), where pointer identity already settles the question — no Python call needed. Add `Py::as_ptr` pointer comparison before the `Python::attach` branch on all three impls (scalar / aggregate / window). Falls through to the GIL-bound `__eq__` only when the two callables are distinct objects. No behavior change for unequal-pointer cases; the equal-pointer case now skips the GIL roundtrip entirely. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent cea9254 commit 2f241be

3 files changed

Lines changed: 28 additions & 13 deletions

File tree

crates/core/src/udaf.rs

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -238,12 +238,16 @@ impl PartialEq for PythonFunctionAggregateUDF {
238238
&& self.signature == other.signature
239239
&& self.return_type == other.return_type
240240
&& self.state_fields == other.state_fields
241-
&& Python::attach(|py| {
242-
self.accumulator
243-
.bind(py)
244-
.eq(other.accumulator.bind(py))
245-
.unwrap_or(false)
246-
})
241+
// Pointer-identity fast path: `Arc`-shared clones of the
242+
// same UDF skip the GIL roundtrip. Falls through to Python
243+
// `__eq__` only for two distinct callables.
244+
&& (self.accumulator.as_ptr() == other.accumulator.as_ptr()
245+
|| Python::attach(|py| {
246+
self.accumulator
247+
.bind(py)
248+
.eq(other.accumulator.bind(py))
249+
.unwrap_or(false)
250+
}))
247251
}
248252
}
249253

crates/core/src/udf.rs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,14 @@ impl PartialEq for PythonFunctionScalarUDF {
106106
self.name == other.name
107107
&& self.signature == other.signature
108108
&& self.return_field == other.return_field
109-
&& Python::attach(|py| self.func.bind(py).eq(other.func.bind(py)).unwrap_or(false))
109+
// Identical pointers ⇒ same Python object. Most equality
110+
// checks compare `Arc`-shared clones of the same UDF
111+
// (e.g. expression rewriting), so the pointer match short-
112+
// circuits before touching the GIL.
113+
&& (self.func.as_ptr() == other.func.as_ptr()
114+
|| Python::attach(|py| {
115+
self.func.bind(py).eq(other.func.bind(py)).unwrap_or(false)
116+
}))
110117
}
111118
}
112119

crates/core/src/udwf.rs

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -319,12 +319,16 @@ impl PartialEq for PythonFunctionWindowUDF {
319319
self.name == other.name
320320
&& self.signature == other.signature
321321
&& self.return_type == other.return_type
322-
&& Python::attach(|py| {
323-
self.evaluator
324-
.bind(py)
325-
.eq(other.evaluator.bind(py))
326-
.unwrap_or(false)
327-
})
322+
// Pointer-identity fast path: `Arc`-shared clones of the
323+
// same UDF skip the GIL roundtrip. Falls through to Python
324+
// `__eq__` only for two distinct callables.
325+
&& (self.evaluator.as_ptr() == other.evaluator.as_ptr()
326+
|| Python::attach(|py| {
327+
self.evaluator
328+
.bind(py)
329+
.eq(other.evaluator.bind(py))
330+
.unwrap_or(false)
331+
}))
328332
}
329333
}
330334

0 commit comments

Comments
 (0)