The first access to any operator namespace paid a numba compile it did
not need. BinaryOp._initialize ended with a loop over
_BUILTIN_UDT_BINARY_OPS that seeded _udt_types/_udt_ops/_custom_dtype via
getattr(binary, op_name). floordiv is the one name in that set registered
with lazy=True, so the getattr materialized it, compiling every dtype
signature on every process start.
Seed those three attributes in __init__ instead. That covers every path
that creates a BinaryOp (builtin enumeration, the specials in
_initialize, delayed UDF materialization, anonymous), so the seeding is
not lost, and laziness survives: floordiv still compiles on first use and
UDT lift behavior is unchanged.
Measured here, fresh process, first `binary.plus` access: ~648 ms before,
~152 ms after (medians of 3 and 5 runs). Treat these as indicative, not
benchmark-grade; load average was ~4 on a shared machine throughout, so
the absolute numbers move but the ~4x gap does not.
This does not make imports free. The ~152 ms that remains is the rest of
_initialize plus namespace setup and is untouched here. It also does not
change what floordiv costs once you use it; the compile is deferred, not
removed. The other four lazily-registered UDFs (rfloordiv, absfirst,
abssecond, rpow) were never in _BUILTIN_UDT_BINARY_OPS and so were never
force-built by this loop.
test_initialize_does_not_build_lazy_udfs covers the invariant in a
subprocess, since _initialize has already run by the time any in-process
test executes.
The first access to any operator namespace paid a numba compile it did
not need. BinaryOp._initialize ended with a loop over
_BUILTIN_UDT_BINARY_OPS that seeded _udt_types/_udt_ops/_custom_dtype via
getattr(binary, op_name). floordiv is the one name in that set registered
with lazy=True, so the getattr materialized it, compiling every dtype
signature on every process start.
Seed those three attributes in init instead. That covers every path
that creates a BinaryOp (builtin enumeration, the specials in
_initialize, delayed UDF materialization, anonymous), so the seeding is
not lost, and laziness survives: floordiv still compiles on first use and
UDT lift behavior is unchanged.
Measured here, fresh process, first binary.plus access: ~648 ms before,
~152 ms after (medians of 3 and 5 runs). Treat these as indicative, not
benchmark-grade; load average was ~4 on a shared machine throughout, so
the absolute numbers move but the ~4x gap does not.
This does not make imports free. The ~152 ms that remains is the rest of
_initialize plus namespace setup and is untouched here. It also does not
change what floordiv costs once you use it; the compile is deferred, not
removed. The other four lazily-registered UDFs (rfloordiv, absfirst,
abssecond, rpow) were never in _BUILTIN_UDT_BINARY_OPS and so were never
force-built by this loop.
test_initialize_does_not_build_lazy_udfs covers the invariant in a
subprocess, since _initialize has already run by the time any in-process
test executes.
Stack created with GitHub Stacks CLI • Give Feedback 💬