Add tools/memory_snapshot.py to measure startup memory footprint - #15358
Open
Carreau wants to merge 1 commit into
Open
Add tools/memory_snapshot.py to measure startup memory footprint#15358Carreau wants to merge 1 commit into
Carreau wants to merge 1 commit into
Conversation
The repository has tooling for import *time* (tools/importtime_average.py) but nothing for memory. The two questions are different: a module can be slow to import but cheap to keep (compiles once, retains little), or fast to import but expensive (unmarshals megabytes of tables), and caches built during InteractiveShell construction cost memory without appearing in `-X importtime` at all. This script measures peak RSS and sys.modules growth for a set of scenarios, each in a fresh subprocess so that already-imported modules cannot mask the cost of the next measurement. A bare-interpreter baseline is subtracted so the numbers are attributable. `--tracemalloc` attributes allocations to source lines when RSS alone is not enough to find the culprit. Defaults cover `import IPython`, the terminal shell, the ipykernel shell, and the heavy third-party dependencies; `-c`/`-m` measure arbitrary statements or modules. Scenarios that fail (an optional dependency that is not installed) are reported as unavailable rather than aborting the run. Co-Authored-By: Claude Opus 5 <[email protected]> Claude-Session: https://claude.ai/code/session_013VnyCiqvakdsX2fhs2R8T5
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.
Motivation
The recent startup-performance work (#15310, #15333, and the open #15340 / #15353) is well served by
tools/importtime_average.py, but that tool only answers "what is slow to import?". There is currently no tooling at all for memory — notracemalloc,memory_profiler,objgraph, orpympleranywhere in the repo.Those are genuinely different questions. A module can be slow to import but cheap to keep around (compiles once, retains little), or fast to import but expensive (unmarshals megabytes of tables). More importantly, caches built during
InteractiveShellconstruction cost memory without appearing in-X importtimeat all — thePdbframe-pinninglru_cachefixed in #15310 is exactly that shape of bug, and import-time profiling could never have surfaced it.What this adds
A single self-contained script, deliberately mirroring the style and CLI conventions of
tools/importtime_average.py.Each measurement runs in a fresh subprocess. This is the central design point: imports are sticky, so once
prompt_toolkitis insys.modules, asking whatwcwidthcosts tells you nothing. A bare-interpreter baseline is measured and subtracted so the reported numbers are attributable.Two metrics per scenario:
resource.getrusage, i.e. what the OS actually charges, including the unmarshalled.pyccode objects that dominate startup.ru_maxrssis KiB on Linux but bytes on macOS; the script handles the difference rather than silently reporting numbers 1024× wrong.sys.modulesgrowth. A good proxy for import-graph breadth and far less noisy than RSS.Defaults cover
import IPython, the terminal shell, the ipykernel shell, and the heavy third-party dependencies.-c/-mmeasure arbitrary statements or modules.--tracemallocattributes allocations to source lines when RSS alone is not enough to locate the culprit.--jsonemits raw data for scripting. Scenarios that fail — typically an optional dependency that is not installed — are reported asunavailablerather than aborting the run, so the tool is useful in a minimal environment.Sample output
Measurements are independent and therefore overlap (
jupyter_clientandIPythonboth pay fortraitlets); the script says so explicitly rather than inviting readers to sum the rows.Observations from running it
Recorded here because they bear on where further optimisation effort is and is not worth spending:
ZMQInteractiveShellsubclassesInteractiveShell, notTerminalInteractiveShell, soprompt_toolkit,wcwidthandjediare never imported under ipykernel — verified directly. The terminal's dominant cost isprompt_toolkit; the kernel's isjupyter_client.jupyter_client's import graph pulls inemail(13 modules),dateutil(11) andcolorama(6). Those look incidental rather than necessary, but they are upstream of this repository.wcwidthcosts ~3.7 MiB and declares a__lazy_modules__list, but on Python < 3.15 its__init__.pyeagerly performs 18from .x import yimports anyway, so the lazy machinery does not currently take effect on supported versions. Also upstream.IPython/terminal/ptutils.pyimportswcwidthsolely to testwcwidth(ch) == 0at two lines, which is a combining-mark test thatunicodedata.combining()(already imported in that file) answers directly. Not proposed here, becauseprompt_toolkit.utilsimportswcwidthunconditionally andptutilsimportsprompt_toolkitin the same file — so the change would save exactly zero memory and is a readability matter only.This PR adds no runtime code and changes no behaviour; it is tooling only.
Generated by Claude Code