Skip to content

feat: optimize cold-boot time with frozen encodings and snapshot support#314

Closed
ppenna wants to merge 1 commit into
nanvix/v3.12.3from
enhancement-snapshot-boot-optimization
Closed

feat: optimize cold-boot time with frozen encodings and snapshot support#314
ppenna wants to merge 1 commit into
nanvix/v3.12.3from
enhancement-snapshot-boot-optimization

Conversation

@ppenna

@ppenna ppenna commented Mar 29, 2026

Copy link
Copy Markdown

Summary

Optimize CPython 3.12 boot time on Nanvix microvm (Windows/WHP) from 1998ms to 172ms (p50).

Changes

Freeze encodings into binary

  • Uncomment <encodings.*> in Tools/build/freeze_modules.py to freeze all encoding modules
  • Eliminates ramfs I/O at startup (was ~50% of boot time)

Reduce binary size (47MB -> 12MB)

  • Remove unused static libraries: sqlite3, openssl, bzip2 from Makefile.nanvix
  • Update .nanvix/nanvix.toml to drop corresponding dependencies

Add cross-compilation sizeof cache

  • Add 18 ac_cv_sizeof_* variables to Makefile.nanvix for proper cross-compilation

VM snapshot support

  • Add snapshot trigger after Py_Initialize in Modules/main.c
  • Standalone assembly file Modules/nanvix_snapshot.S survives LTO elimination
  • On first boot, creates VM snapshot; subsequent boots restore in ~35ms

Performance

Configuration p50 p95
Baseline (47MB, ramfs) 1998 ms 5256 ms
Optimized cold (12MB, frozen) 825 ms 860 ms
Snapshot restore 172 ms 193 ms

Companion PR: nanvix/nanvix (snapshot restore CLI flag)

- 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>
Copilot AI review requested due to automatic review settings March 29, 2026 22:04

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.path to ntpath unconditionally. This is host-dependent output from freeze_modules.py and can break non-Windows targets; consider restoring the previous posixpath mapping 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.

Comment thread Python/frozen.c
Comment on lines 196 to 200
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 */

Copilot AI Mar 29, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot uses AI. Check for mistakes.
Comment thread Python/frozen.c
Comment on lines +336 to +337
{"posixpath", NULL, 0, false, GET_CODE(posixpath)},
{"os.path", NULL, 0, false, GET_CODE(ntpath)},

Copilot AI Mar 29, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
{"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

Copilot uses AI. Check for mistakes.
Comment thread Modules/main.c
Comment on lines +748 to +752
* On restore, execution resumes here and proceeds to Py_RunMain(). */
{
register int _eax __asm__("eax") = 35;
__asm__ volatile("int $0x80" : "+r"(_eax) : : "edx", "memory");
}

Copilot AI Mar 29, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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).

Copilot uses AI. Check for mistakes.
Comment thread Modules/main.c
Comment on lines +14 to +18
#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);

Copilot AI Mar 29, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot uses AI. Check for mistakes.
Comment thread Makefile.nanvix
Comment on lines +208 to 212
@# 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

Copilot AI Mar 29, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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).

Copilot uses AI. Check for mistakes.
@ppenna

ppenna commented Mar 30, 2026

Copy link
Copy Markdown
Author

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.

@ppenna ppenna closed this Mar 30, 2026
@ppenna ppenna deleted the enhancement-snapshot-boot-optimization branch May 1, 2026 21:10
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.

2 participants