Skip to content

Stop building floordiv at import time when seeding UDT auto-lift - #610

Open
eriknw wants to merge 1 commit into
29-io-config-coveragefrom
30-udf-import-cost
Open

Stop building floordiv at import time when seeding UDT auto-lift#610
eriknw wants to merge 1 commit into
29-io-config-coveragefrom
30-udf-import-cost

Conversation

@eriknw

@eriknw eriknw commented Aug 4, 2026

Copy link
Copy Markdown
Member

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 CLIGive Feedback 💬

@eriknw
eriknw marked this pull request as ready for review August 4, 2026 16:07
@eriknw
eriknw force-pushed the 30-udf-import-cost branch 3 times, most recently from b434a55 to bebe448 Compare August 5, 2026 03:18
@eriknw
eriknw force-pushed the 30-udf-import-cost branch from bebe448 to 924625d Compare August 5, 2026 17:44
@eriknw
eriknw force-pushed the 30-udf-import-cost branch from 924625d to 59b2c9e Compare August 5, 2026 18:03
@eriknw
eriknw force-pushed the 30-udf-import-cost branch 2 times, most recently from 9fc661a to 82279ee Compare August 6, 2026 07:59
@eriknw
eriknw force-pushed the 30-udf-import-cost branch from 82279ee to 7eb2423 Compare August 6, 2026 15:39
@eriknw
eriknw force-pushed the 30-udf-import-cost branch 2 times, most recently from 4323f4f to 84ca399 Compare August 6, 2026 20:36
@eriknw
eriknw force-pushed the 30-udf-import-cost branch from 84ca399 to 27b2b3e Compare August 6, 2026 20:42
@eriknw
eriknw force-pushed the 30-udf-import-cost branch from 27b2b3e to 37c456e Compare August 7, 2026 02:48
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.
@eriknw
eriknw force-pushed the 30-udf-import-cost branch from 37c456e to 03ff41f Compare August 7, 2026 05:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant