Skip to content

Add R01, before your first line - #178

Merged
tamnd merged 1 commit into
mainfrom
r01-before-your-first-line
Sep 5, 2026
Merged

Add R01, before your first line#178
tamnd merged 1 commit into
mainfrom
r01-before-your-first-line

Conversation

@tamnd

@tamnd tamnd commented Sep 5, 2026

Copy link
Copy Markdown
Owner

The first of the runtime lessons, and a change of subject. Everything up to here has been about what happens when your code runs. This one is about everything that has already happened by the time it starts.

How it is put together

Eight cells, and every one of them starts a child interpreter and asks it a question rather than looking at the notebook it is running in. That is not a stylistic choice. The notebook finished starting up long ago and has since imported hundreds of modules that have nothing to do with the subject, so it cannot be a witness about its own startup. All eight cells check whether they can start a process at all and say so rather than failing, which is what makes the lesson read through in a browser tab.

What is in it

The inventory. A child counts its own sys.modules and splits it by where each module came from: C compiled into the binary, Python bytecode frozen into the binary, or a path, which means somebody opened a file. With -S there are 21 modules and not one of them was read from a file. The cell also names the ones that did come from a file on a normal start, which on most installs is whatever site brought in for you rather than anything the interpreter needed.

The two halves. Prose, with the chain from pymain_main through pymain_init and Py_InitializeFromConfig to Py_RunMain. Core startup builds the runtime and the built in types with no import system, no sys.path and no codecs. Main startup is everything that needs those, including working out sys.path. That split is why a configuration mistake comes out as a fatal error with a plain C string: at the moment it is noticed there is nothing to raise. Then -X importtime in a child, which prints exactly the imports that happen before your first line and nothing else.

The configuration. Seven children with different combinations of PYTHONOPTIMIZE, -O, -OO and -E, all reporting sys.flags.optimize. The natural guess is that the command line beats the environment because it is nearer, and it is wrong. The helper that reads a flag out of the environment is if (*flag < value) *flag = value;, which raises a floor and never lowers anything, and -O on the command line increments rather than assigns. So the answer is the highest number anybody asked for. PYTHONOPTIMIZE=2 left in a shell profile is not undone by passing -O today, and nothing tells you. -E is the only way out, and it works by deleting the environment rather than outranking it.

The path. sys.path is not computed in C. It is computed by Modules/getpath.py, compiled to bytecode when CPython is built, stored in the binary as a marshalled blob, unmarshalled during main startup and evaluated. It cannot import anything, because sys.path is what it exists to produce, so it is handed eleven C functions to stand in for os.path: abspath, basename, dirname, hassuffix, isabs, isdir, isfile, isxfile, joinpath, readlines, realpath. The cell prints the whole path configuration with an exists check next to every entry, which makes the pythonXY.zip that is not there stand out. That entry is deliberate, not leftover.

The front of the path. sys.path[0] compared across -c, a script, -m and -P. It is pushed on after startup is finished, by the code that is about to hand control to your program, and it is the directory of your script or the current directory. That is the complete mechanism behind a file called random.py next to your script shadowing the standard library. -P does not remove the entry, it never adds it, so sys.path[0] becomes that zip file instead.

The cost. Ten timed starts of an interpreter that runs nothing, with and without site.

The recordings

Two Tier 1 recordings, and the first release against debug pair in the book. Same program, same image pipeline, one build configured with --with-pydebug.

The debug build starts in 56.5 ms against 26.5 ms. The easy explanation is the assertions and the reference count bookkeeping, and the easy explanation is mostly wrong. Release reports 17 frozen modules and 0 from a file. Debug reports 3 frozen and 14 from a file. That is not the same work done more slowly, it is different work. A debug build turns frozen modules off in the defaults, one #ifdef, so that somebody debugging CPython steps through the real Lib/os.py rather than through bytecode baked in at build time. The import bill goes from 11.6 ms to 30.8 ms with it, and -X frozen_modules=on puts it back.

Also

A new glossary group for startup and shutdown with three terms: two phase initialisation, path configuration, safe path. GLOSSARY.md is now 216 terms. Six diagrams, sixteen citations, both READMEs updated, CLAIMS.md is 564 claims across 72 lessons.

just check and just versions are green locally, 167 declared and 181 noted across 72 notebooks, and the browser probe has been rerun. All eight R01 cells report that the runtime cannot start another interpreter and carry on.

Part of #27.

The first runtime lesson. Everything so far has been about what happens
when your code runs. This one is about what has already happened by the
time it starts.

Eight cells, every one of them asking a child interpreter rather than the
notebook, because a notebook is far too late to be a fair witness about
its own startup. A count of sys.modules split by where each module came
from, with and without site. -X importtime adding up every import that
runs before the first line. Seven children disagreeing about
sys.flags.optimize. The whole path configuration printed with an exists
check next to each entry. sys.path[0] compared across -c, a script, -m
and -P. And ten timed starts of an interpreter that runs nothing.

The facts worth having. With site out of the way, none of the modules
loaded at startup were read from a file, because they are either C
compiled into the binary or Python bytecode frozen into it. Startup is
two halves on purpose and the first half has no import system, which is
why a configuration mistake is a fatal error with a C string rather than
a traceback. The command line and the environment settle a disagreement
by taking the higher number rather than the nearer one, so
PYTHONOPTIMIZE=2 survives a later -O. sys.path is produced by a Python
program frozen into the binary and handed eleven C functions to stand in
for the os.path it cannot import. And the front of sys.path is pushed on
after startup is over, which is the whole mechanism behind a local
random.py shadowing the standard library.

Two Tier 1 recordings, the first release against debug pair in the book.
The debug build starts in 56.5 ms against 26.5 ms, and the interesting
part is that most of the extra is not the assertions. Release reports 17
frozen modules and none from a file. Debug reports 3 frozen and 14 from a
file, because a debug build turns frozen modules off in the defaults so
that you can step through the real Lib/os.py. The import bill goes from
11.6 ms to 30.8 ms with it.

Three glossary terms in a new startup group: two phase initialisation,
path configuration and safe path. Six diagrams. Sixteen citations.

Part of #27.
@tamnd tamnd added this to the M8 Concurrency and runtime milestone Sep 5, 2026
@tamnd tamnd added kind/lesson A chapter: prose, notebook, experiments, boss fight area/runtime Startup, shutdown, import, extension modules and the C API labels Sep 5, 2026
@tamnd
tamnd merged commit e3eedf3 into main Sep 5, 2026
16 checks passed
@tamnd
tamnd deleted the r01-before-your-first-line branch September 5, 2026 17:34
@tamnd tamnd mentioned this pull request Sep 5, 2026
5 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/runtime Startup, shutdown, import, extension modules and the C API kind/lesson A chapter: prose, notebook, experiments, boss fight

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant