feat: optimize cold-boot time with frozen encodings and snapshot support#314
feat: optimize cold-boot time with frozen encodings and snapshot support#314ppenna wants to merge 1 commit into
Conversation
- Freeze all encodings into the binary (eliminates ramfs I/O at startup) - Remove unused static libraries (sqlite3, openssl, bzip2): 47MB -> 12MB - Add cross-compilation sizeof cache variables for Nanvix builds - Add VM snapshot trigger after Py_Initialize for near-instant warm boots - Standalone assembly file (nanvix_snapshot.S) to survive LTO elimination Cold boot: 1998ms -> 825ms (p50) Snapshot restore: 172ms (p50), 193ms (p95) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
This PR targets substantially faster CPython 3.12 cold-boot on Nanvix microvm by eliminating early filesystem I/O (freezing encodings), shrinking link dependencies, and adding a post-Py_Initialize() VM snapshot trigger.
Changes:
- Freeze
encodings.*into the binary and regenerate frozen/deepfreeze build metadata across Makefile/PCbuild outputs. - Reduce Nanvix build inputs by dropping sqlite/openssl/bzip2 linkage/dependencies and adding cross-compilation
ac_cv_sizeof_*cache entries. - Add a Nanvix snapshot syscall trigger after interpreter initialization plus a standalone assembly helper intended to survive LTO.
Reviewed changes
Copilot reviewed 9 out of 9 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
| Tools/build/freeze_modules.py | Enables freezing of encodings.* as part of startup frozen stdlib set. |
| Python/frozen.c | Regenerates frozen module tables to include encodings and switches entries to deepfreeze get_code usage. |
| PCbuild/_freeze_module.vcxproj.filters | Adds encodings/* sources into the Windows freeze module project filters. |
| PCbuild/_freeze_module.vcxproj | Adds encodings/* modules to the freeze module project and deepfreeze inputs. |
| Modules/nanvix_snapshot.S | Introduces an assembly syscall helper for VM snapshot triggering. |
| Modules/main.c | Triggers snapshot after initialization (Nanvix-only) via inline int 0x80. |
| Makefile.pre.in | Expands frozen/deepfreeze inputs/targets to include all encoding modules. |
| Makefile.nanvix | Removes unused libs, adds sizeof cache vars, and injects snapshot object into the link. |
| .nanvix/nanvix.toml | Drops sqlite/openssl/bzip2 dependencies, keeping zlib/libffi. |
Comments suppressed due to low confidence (1)
Python/frozen.c:371
- The frozen module alias table now maps
os.pathtontpathunconditionally. This is host-dependent output fromfreeze_modules.pyand can break non-Windows targets; consider restoring the previousposixpathmapping or making this alias conditional on the target platform.
{"_frozen_importlib_external", "importlib._bootstrap_external"},
{"encodings.__init__", "<encodings"},
{"os.path", "ntpath"},
{"__hello_alias__", "__hello__"},
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| static const struct _frozen bootstrap_modules[] = { | ||
| {"_frozen_importlib", _Py_M__importlib__bootstrap, (int)sizeof(_Py_M__importlib__bootstrap), false, GET_CODE(importlib__bootstrap)}, | ||
| {"_frozen_importlib_external", _Py_M__importlib__bootstrap_external, (int)sizeof(_Py_M__importlib__bootstrap_external), false, GET_CODE(importlib__bootstrap_external)}, | ||
| {"zipimport", _Py_M__zipimport, (int)sizeof(_Py_M__zipimport), false, GET_CODE(zipimport)}, | ||
| {"_frozen_importlib", NULL, 0, false, GET_CODE(importlib__bootstrap)}, | ||
| {"_frozen_importlib_external", NULL, 0, false, GET_CODE(importlib__bootstrap_external)}, | ||
| {"zipimport", NULL, 0, false, GET_CODE(zipimport)}, | ||
| {0, 0, 0} /* bootstrap sentinel */ |
There was a problem hiding this comment.
All frozen module entries now set code = NULL and size = 0 and rely solely on get_code. In Python/import.c, get_code is only used for the main interpreter; subinterpreters fall back to unmarshalling info->data, which will be NULL here and can crash. Keep the marshalled code bytes for frozen modules (as before), or adjust the import machinery so subinterpreters never attempt to unmarshal from a NULL buffer for deepfrozen-only modules.
| {"posixpath", NULL, 0, false, GET_CODE(posixpath)}, | ||
| {"os.path", NULL, 0, false, GET_CODE(ntpath)}, |
There was a problem hiding this comment.
os.path is now listed as a frozen module backed by GET_CODE(ntpath) unconditionally. For non-Windows targets this should typically point at posixpath; otherwise import os.path will load the wrong path implementation.
This issue also appears on line 368 of the same file.
| {"posixpath", NULL, 0, false, GET_CODE(posixpath)}, | |
| {"os.path", NULL, 0, false, GET_CODE(ntpath)}, | |
| {"posixpath", NULL, 0, false, GET_CODE(posixpath)}, | |
| #ifdef MS_WINDOWS | |
| {"os.path", NULL, 0, false, GET_CODE(ntpath)}, | |
| #else | |
| {"os.path", NULL, 0, false, GET_CODE(posixpath)}, | |
| #endif |
| * On restore, execution resumes here and proceeds to Py_RunMain(). */ | ||
| { | ||
| register int _eax __asm__("eax") = 35; | ||
| __asm__ volatile("int $0x80" : "+r"(_eax) : : "edx", "memory"); | ||
| } |
There was a problem hiding this comment.
This inline int $0x80 syscall block is missing correct clobber constraints (at least ecx, cc, and potentially others) and ignores the syscall return value, which risks miscompilation under optimization and makes it hard to distinguish snapshot-create vs snapshot-restore. Prefer calling the nanvix_snapshot() helper (in the separate .S file) and define the asm there with the proper ABI/clobbers and a return code you can branch on (and/or gate via an env/flag).
| #ifdef __nanvix__ | ||
| #include <stdlib.h> // getenv() | ||
| #include <string.h> // strcmp() | ||
| /* Defined in Modules/nanvix_snapshot.S — separate .S file survives LTO. */ | ||
| extern void nanvix_snapshot(void); |
There was a problem hiding this comment.
getenv(), strcmp(), and the nanvix_snapshot() declaration are currently unused in this file. Either use them (e.g., to gate snapshot behavior via an env var and call the helper function) or remove the extra includes/extern to avoid dead code and keep the Nanvix-specific block minimal.
| @# Compile nanvix snapshot helper (outside LTO, so it survives optimization) | ||
| $(DOCKER_RUN) $(DOCKER_TOOLCHAIN_PATH)/bin/i686-nanvix-gcc -c Modules/nanvix_snapshot.S -o Modules/nanvix_snapshot.o | ||
| @# Inject the snapshot object into the generated Makefile's link objects | ||
| $(DOCKER_RUN) sh -c 'grep -q "nanvix_snapshot.o" Makefile || sed -i "/Modules\/main\.o/s|$$| Modules/nanvix_snapshot.o|" Makefile' | ||
| $(DOCKER_RUN) make -j$$(nproc) all |
There was a problem hiding this comment.
The snapshot object is only compiled/linked in the Docker build path, and it’s injected into the generated Makefile via a sed edit. If Modules/main.c ends up calling nanvix_snapshot(), the non-Docker build will fail to link, and the sed injection is brittle if the Makefile layout changes. Consider adding Modules/nanvix_snapshot.o via a supported make variable/configure mechanism (or apply the same logic to both build paths and fail the build if the injection pattern doesn’t match).
|
This PR has been split into 4 focused, independently reviewable PRs:
All review-comment fixes (subinterpreter guard, os.path platform conditional, inline asm replacement, unused include removal, Makefile robustness) are incorporated into the appropriate split PRs. |
Summary
Optimize CPython 3.12 boot time on Nanvix microvm (Windows/WHP) from 1998ms to 172ms (p50).
Changes
Freeze encodings into binary
<encodings.*>inTools/build/freeze_modules.pyto freeze all encoding modulesReduce binary size (47MB -> 12MB)
Makefile.nanvix.nanvix/nanvix.tomlto drop corresponding dependenciesAdd cross-compilation sizeof cache
ac_cv_sizeof_*variables toMakefile.nanvixfor proper cross-compilationVM snapshot support
Py_InitializeinModules/main.cModules/nanvix_snapshot.Ssurvives LTO eliminationPerformance
Companion PR: nanvix/nanvix (snapshot restore CLI flag)