Support baking PythonCall into a juliacall system image (opt-in)#773
Open
ncudlenco wants to merge 1 commit into
Open
Support baking PythonCall into a juliacall system image (opt-in)#773ncudlenco wants to merge 1 commit into
ncudlenco wants to merge 1 commit into
Conversation
When PythonCall is compiled into a juliacall system image, its __init__ runs during jl_init_with_image, before juliacall's bootstrap defines Main.__PythonCall_libptr. Embedding was therefore mis-detected as non-embedded and failed with "'juliacall' module already exists". Add an opt-in embedded preference / JULIA_PYTHONCALL_EMBEDDED (via the same getpref mechanism as exe/lib) that forces the embedded path and obtains libpython from the lib preference / JULIA_PYTHONCALL_LIB (already loaded in the host process). Unset, behaviour is unchanged. Docs and CHANGELOG updated.
This was referenced May 19, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Lets
PythonCallitself be compiled into a juliacall system image (opt-in). With it baked in, theusing PythonCallthatimport juliacallperforms becomes a memory-map instead of a multi-second load+compile. In a containerised juliacall workload we measured, fresh-process startup drops from ~18 s to ~1.9 s. Off by default — no behaviour change unless enabled. It's meant as a low-risk interim until the automatic fix discussed in #436 (or related work) lands; it doesn't conflict with or preclude that.Motivation
In short-lived Python processes that embed Julia via juliacall — serverless or autoscaled containers (AWS Lambda, queue workers, CI jobs) that start, handle one request, and exit — there's no long-lived process to amortise Julia start-up. Every cold start pays it again, and the dominant part is the
using PythonCallthatimport juliacallruns to bring the bridge up.Measured, fresh container to first call:
PythonCallalso baked into the sysimage (this PR)(~0.68 s of the ~1.9 s is the Julia stack; the rest is container/runtime overhead common to any container.) The ~16 s saved is essentially the repeated
using PythonCallload+compile.PYTHON_JULIACALL_SYSIMAGEalready exists, but a sysimage with only the application packages still runsusing PythonCallevery cold start. BakingPythonCallin too is the obvious fix, but it currently fails: whenPythonCallis in the sysimage its__init__runs duringjl_init_with_image, before juliacall's bootstrap has setMain.__PythonCall_libptr.init_context()decides "embedded" only from that global, so it sees "not embedded"; since Python is mid-import juliacall,init_juliacall()then errors with'juliacall' module already exists.Change
A second, opt-in way to detect the embedded case, using the same
getprefmechanism added in 0.9.33 (preferenceembedded/ envJULIA_PYTHONCALL_EMBEDDED, defaultno):src/Utils/Utils.jl— addgetpref_embedded(), alongside the existinggetpref_exe/getpref_lib/getpref_pickle.src/C/context.jl— if theMainglobal isn't set butembeddedis, take the embedded path and get libpython from the existinglibpreference /JULIA_PYTHONCALL_LIB(it's already loaded in the host process, sodlopenjust hands back a handle).docs/src/juliacall.md— a short "Baking PythonCall into a system image" section.CHANGELOG.md— Unreleased entry.In practice it's used together with
PYTHON_JULIACALL_SYSIMAGE(PythonCall baked in) andPYTHON_JULIACALL_EXE/PROJECT.Backward compatibility
It is backwards compatible since the option is off by default. The new path is only reached when
embeddedis explicitly set andMain.__PythonCall_libptris absent. The normal juliacall path and the PythonCall-as-host path are unchanged.Testing
The new code path only runs when explicitly enabled, so default behaviour is unchanged and the existing test suites are unaffected.
It was verified manually: with
PythonCallbaked into a system image andembeddedenabled,import juliacalland calling Julia both work. No automated test is included because exercising it requires building a system image in CI, which is slow — happy to add one, or document the steps, if you'd prefer.Related issues
Relates to #436 and #600 — this unblocks their use case (baking PythonCall into a custom system image) as an interim. The root-cause fix the maintainer described in #436 — resetting PythonCall's sysimage-persisted state in
__init__so it works with no opt-in — is the longer-term path; this PR doesn't attempt or preclude it.Also relevant to #762 ("Improve juliacall startup time?", which explicitly asks about compiling a system image) and a building block for #76 ("Compile and use custom sysimages automatically").
Does not address #129 (a different failure:
no environment in the LOAD_PATH depends on CondaPkg).