Add R02, where the state lives - #180
Merged
Merged
Conversation
A running Python is three nested things: a runtime that there is one of per process, interpreters inside it, and threads inside those. Every fact this book has taught you belongs to exactly one of the three levels, and the level decides who can see a change when you make one. The lesson finds the boundaries by experiment rather than by assertion. Make a second interpreter, ask both of them the same question, and watch where the answers stop matching. Eight cells, twenty three in total, fifteen citations, six diagrams and seven claims. Two interpreters return the same id for None, the booleans, the small ints, one character strings and the built in type objects, because all of those are fields of the runtime struct rather than allocations, and different ids for everything else. Where the small int run ends is a define rather than a policy, and a cell finds the edge by asking both interpreters for the address of every int in a range. It is 256 on 3.14 and 1024 on 3.15, which is declared on the cell. An interpreter owns its own sys.modules, its own builtins, its own import lock, its own warnings filters and its own recursion limit, so a change to any of them is invisible next door. Signals are the exception: one handler table for the whole process, with the main thread of the main interpreter as the only writer, which is two conditions in one line of C and is exactly what the error message says. A thread owns the exception it is handling, and two threads inside an except block at the same instant each see their own. The lesson says out loud why no cell calls sys._current_frames from inside a second interpreter. On a build with the GIL that aborts the process, because the function materialises frame objects for other interpreters and each interpreter allocates from its own pools. That is issue #179, found while writing this. Two Tier 1 recordings put a price on it, one on a release build and one on a free threaded build. An interpreter costs about fifty times what an operating system thread costs to make, plus a couple of megabytes to keep, and the free threaded build charges more for both because mimalloc heaps and obmalloc pools do not price an extra interpreter the same way. Also adds two glossary terms, runtime state and static object, and the R02 rows to both READMEs and to the experiments index. GLOSSARY.md is 218 terms and CLAIMS.md is 571 claims across 73 lessons. Part of #27.
5 tasks
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.
R02 is the second of the nine runtime lessons, and it is the one that puts a frame around everything before it. A running Python is three nested things: a runtime that there is one of per process, interpreters inside it, and threads inside those. Every fact this book has taught you belongs to exactly one of those three levels, and the level decides who can see a change when you make one.
The method is the same in every section. Make a second interpreter, ask both of them the same question, and watch where the answers stop matching.
Eight cells. Nine objects asked for their address on both sides. A search for the exact integer at which the two interpreters stop agreeing. The interpreter list, with ids counting upwards and never coming back. The recursion limit, a warnings filter and
sys.moduleschanged on one side and read on the other.signal.signaltried from the main thread, from another thread, and from inside a second interpreter. Two threads each holding a different exception at the same instant.The facts worth keeping. The shared pile is small and it is not shared by luck:
None, the booleans, the small ints, the one character strings, the fixed identifier strings and the static type objects are fields of the runtime struct rather than allocations, which is why nothing can free them and why every interpreter in the process points at the same bytes. Where the small int run ends is a#definerather than a policy, and it moved from 256 in 3.14 to 1024 in 3.15, which the cell measures rather than asserts. Everything else you think of as belonging to Python belongs to one interpreter: its ownsys.modules, its own builtins, its own import lock, its own warnings filters, its own recursion limit. Signals are the exception, because the operating system has never heard of interpreters, so there is one handler table per process and the test for who may write to it is two conditions in one line of C, one for each of the two upper levels. And the exception being handled is per thread, which two threads inside anexceptblock at the same instant make obvious.The lesson also says out loud why no cell calls
sys._current_framesfrom inside a second interpreter. On a build with the GIL that aborts the whole process, because the function materialises frame objects for other interpreters' frames and each interpreter allocates from its own pools. That is #179, found while writing this.Two Tier 1 recordings, one on a release build and one on a free threaded build. An operating system thread takes a few hundred microseconds. An interpreter takes about fifty times that, and keeps a couple of megabytes of resident memory for as long as you hold it. The free threaded build charges more for both, and the reason is the allocator rather than noise: mimalloc heaps and obmalloc pools do not price an extra interpreter the same way.
Also in here: two new glossary terms,
runtime stateandstatic object, six diagrams, fifteen citations, and the R02 rows in both READMEs and in the experiments index. GLOSSARY.md is 218 terms and CLAIMS.md is 571 claims across 73 lessons.Part of #27.