GV

GeezerVison

The World Wide Web, restored to 800 pixels of glory.

Home|About|Gaming cabinet|Archives|How it works|Starter sites

On this site

  • Sign in
  • ActionsCI/CD and automation
  • CodespacesCloud development environments
  • All Features
  • Documentation
  • Training
  • Certifications
  • Build with Copilot
  • Customize Copilot
  • Integrations
  • Trending repos
  • GitHub Sponsors
  • Developer articles
  • Enterprises

This view

  • Original page
  • New address

GeezerVison is showing github.com — gh-129724: gh-156504: Make marshal output independent of reference counts by gpshead · Pull Request #159192 · python/cpython

Conversation

@gpshead

@gpshead gpshead commented Oct 11, 2026 •
edited
Loading

Uh oh!

There was an error while loading. Please reload this page.

Copy link
Copy Markdown
Member

marshal.dumps() could give different bytes for the same value, because the writer used the reference count of each object to decide whether a later occurrence may refer back to it (FLAG_REF). The count depends on what else in the process holds a reference to the object, and on the free-threaded build on which thread owns it. This is the cause of gh-156504 and one of the two causes of gh-129724 (the other is in the compiler: see the companion PR gh-159193).

FLAG_REF is now set on exactly the objects that are written more than once in the dump.

Example

>>> x = float('1.5')
>>> marshal.dumps([float('1.5')]).hex(' ')   # refcount 1
'5b 01 00 00 00 67 00 00 00 00 00 00 f8 3f'
>>> marshal.dumps([x]).hex(' ')              # refcount 2: main sets FLAG_REF (0x80)
'5b 01 00 00 00 e7 00 00 00 00 00 00 f8 3f'

With this PR both calls return the first form. Where an object really is written twice, the index of the reference no longer counts objects that were flagged for nothing:

value: [a, b, b]          a = 1.5 and b = 2.5, both also referenced from elsewhere

main:     [ 3   FLOAT|REF 1.5   FLOAT|REF 2.5   REF #1 ]      a is flagged for nothing, so b is #1
this PR:  [ 3   FLOAT     1.5   FLOAT|REF 2.5   REF #0 ]      only b is flagged, so b is #0

How

The reader numbers the flagged objects in the order it meets them, so the writer cannot know the index of a reference until it knows which earlier objects end up flagged. It still makes a single pass over the objects, and fixes up the output at the end.

flowchart TB
  subgraph before["main"]
    direction TB
    b1["w_ref(obj)"] --> b2{"refcount == 1?"}
    b2 -- yes --> b3["write object<br>no flag"]
    b2 -- no --> b4{"seen before?"}
    b4 -- no --> b5["write object + FLAG_REF<br>index = number flagged so far"]
    b4 -- yes --> b6["write TYPE_REF index"]
  end
  subgraph after["this PR"]
    direction TB
    a1["w_ref(obj)"] --> a2{"refcount == 1?"}
    a2 -- yes --> a3["write object<br>(cannot occur twice)"]
    a2 -- no --> a4{"seen before?"}
    a4 -- no --> a5["write object, no flag yet<br>remember offset of its type byte"]
    a4 -- yes --> a6["mark object shared<br>write TYPE_REF + provisional index<br>remember its position"]
    a5 --> a7
    a6 --> a7["w_resolve_refs(), once, at the end:<br>set FLAG_REF on shared objects,<br>number them, rewrite TYPE_REF indexes"]
  end
Loading
  • A reference count of 1 is still used, but only as a shortcut to skip the hash table: such an object cannot occur twice, so the result is the same without it.
  • Interned strings are no longer flagged unconditionally.
  • The reader is unchanged and the format version is not bumped. Old and new readers load old and new output.
  • PyMarshal_WriteObjectToFile() now builds the data in memory and writes it with one fwrite(), since the flags are only known at the end. Nothing is written if the object cannot be marshalled. Documented in Doc/c-api/marshal.rst.

Compatibility

The bytes of every .pyc change (fewer flags), which is why Programs/test_frozenmain.h is regenerated. marshal.loads() gives the same objects with the same sharing.

The generated pycs can be loaded by prior Python versions, but this kind of change might be considered disruptive for some when considering a backport. Realistically: prior versions are all "broken" by not producing stable output in real world scenarios, so any bug fixes going in that stabilize how things work are going to lead to some newly generated bytecode changing vs what was seen in the past, as already happens between runs compiling the same source file under different process state today.

Related: #159197 is stacked on top of this.

Performance

Release builds, not PGO, compared with main:

default (GIL) free-threaded
dumps of the code objects of the stdlib 3.7% slower 4.3% slower
dumps dominated by references (millions of TYPE_REF) 5-9% slower
dumps of unshared data (1M distinct floats) 32% faster
loads of the new output 3-5% faster

Writer memory grows by 8 bytes per object with a reference count above 1.

Tests

ReferenceTestCase in test_marshal checks exact bytes: single occurrences are not flagged whatever their reference count, interned strings, references at every buffer offset, objects seen before and after the ones they share with, NaNs in sets. test_capi.test_marshal checks that nothing is written on error.


drafted using Claude, reviewed & iterated upon by me.

Fixes #156504

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
@gpshead
gpshead requested review from ZeroIntensity and ericsnowcurrently as code owners October 11, 2026 19:05
@bedevere-app bedevere-app Bot added the type-feature A feature request or enhancement label Oct 11, 2026
@bedevere-app bedevere-app Bot mentioned this pull request Oct 11, 2026
Bytecode compilation output depends on order of files compiled #129724
Open
@bedevere-app bedevere-app Bot added the awaiting core review label Oct 11, 2026
@gpshead gpshead self-assigned this Oct 11, 2026
@gpshead gpshead added type-bug An unexpected behavior, bug, or error stdlib Standard Library Python modules in the Lib/ directory labels Oct 11, 2026
@read-the-docs-community

read-the-docs-community Bot commented Oct 11, 2026 •
edited
Loading

Uh oh!

There was an error while loading. Please reload this page.

Copy link
Copy Markdown

Documentation build overview

📚 cpython-previews | 🛠️ Build #35107691 | 📁 Comparing d69021e against main (e878e1e)

  🔍 Preview build  

12 files changed · ± 12 modified

± Modified

  • builtins/stdtypes.html
  • c-api/marshal.html
  • library/concurrent.interpreters.html
  • library/ctypes.html
  • library/multiprocessing.html
  • library/tarfile.html
  • reference/expressions.html
  • using/android.html
  • using/ios.html
  • using/mac.html
  • and 2 more...
All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

This was referenced Oct 11, 2026
gh-129724: Intern string constants in the compiler, not per code object #159193
Merged
gh-129724: Make marshalled code output deterministic #156862
Closed
@gpshead
gpshead requested a review from methane October 11, 2026 19:23
@gpshead gpshead mentioned this pull request Oct 11, 2026
Concurrent compilation changes marshal output in free-threaded builds #156504
Closed
@gpshead gpshead added 🔨 test-with-buildbots Test PR w/ buildbots; report in status section and removed type-feature A feature request or enhancement labels Oct 11, 2026
@bedevere-bot

bedevere-bot commented Oct 11, 2026

Copy link
Copy Markdown

🤖 New build scheduled with the buildbot fleet by @gpshead for commit 355cb8c 🤖

Results will be shown at:

https://buildbot.python.org/all/#/grid?branch=refs%2Fpull%2F159192%2Fmerge

If you want to schedule another build, you need to add the 🔨 test-with-buildbots label again.

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@bedevere-bot bedevere-bot removed the 🔨 test-with-buildbots Test PR w/ buildbots; report in status section label Oct 11, 2026
This was referenced Oct 11, 2026
gh-159184: Do not compare set elements when marshalling a set #159196
Merged
gh-159184: Make the marshal order of set elements with equal dumps stable #159197
Merged
@gpshead
pythongh-129724: Make marshal output independent of reference counts
7893950
The marshal writer decided whether an object can be referenced later
(FLAG_REF) from its reference count, so the output for the same value
depended on what else in the process held a reference to its parts, and
differed between the default and the free-threaded builds' view of the
same objects.  This is the remaining cause of pythongh-129724 and the cause of
pythongh-156504.

FLAG_REF is now set exactly on the objects which are written more than
once in the dump.  The writer still makes one pass: it records the
position of the type byte of each object it remembers and of each
TYPE_REF, and when the dump is complete it sets the flags and renumbers
the references.  A reference count of one is only used to skip the
hashtable.  Interned strings are no longer flagged unconditionally.

The reader is unchanged and the format version is not bumped: both the
old and the new output are loaded by old and new readers.

PyMarshal_WriteObjectToFile() now builds the data in memory and writes
it with one fwrite(), since the flags are only known at the end.
Nothing is written if the object cannot be marshalled.
@gpshead
gpshead force-pushed the marshal-exact-refs branch from 355cb8c to 7893950 Compare October 11, 2026 22:00
@gpshead
gpshead requested a review from serhiy-storchaka October 11, 2026 22:46
@gpshead gpshead added the 🔨 test-with-buildbots Test PR w/ buildbots; report in status section label Oct 11, 2026
@bedevere-bot

bedevere-bot commented Oct 11, 2026

Copy link
Copy Markdown

🤖 New build scheduled with the buildbot fleet by @gpshead for commit 7893950 🤖

Results will be shown at:

https://buildbot.python.org/all/#/grid?branch=refs%2Fpull%2F159192%2Fmerge

If you want to schedule another build, you need to add the 🔨 test-with-buildbots label again.

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@bedevere-bot bedevere-bot removed the 🔨 test-with-buildbots Test PR w/ buildbots; report in status section label Oct 11, 2026
@gpshead gpshead mentioned this pull request Oct 11, 2026
marshal.dumps() raises TypeError for a set containing several complex NaNs #159184
Closed
@gpshead gpshead changed the title gh-129724: Make marshal output independent of reference counts gh-129724: gh-156504: Make marshal output independent of reference counts Oct 12, 2026
@methane

methane commented Oct 13, 2026

Copy link
Copy Markdown
Member

Could you reduce the number of NEWS entries to one or two? In What's New, the C API and library changes need to be documented separately because they are in different sections, but I think related changes can be combined into a single NEWS entry.

gpshead reacted with thumbs up emoji
All reactions
  • 👍 1 reaction

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

methane
methane approved these changes Oct 13, 2026
View reviewed changes
@bedevere-app bedevere-app Bot removed the awaiting core review label Oct 13, 2026
@bedevere-app bedevere-app Bot added the awaiting merge label Oct 13, 2026
@gpshead
Merge the pythongh-156504 NEWS entry into the pythongh-129724 one
d69021e
@gpshead

gpshead commented Oct 13, 2026

Copy link
Copy Markdown
Member Author

ETOOMUCHNEWS - good call, done.

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@gpshead
gpshead enabled auto-merge (squash) October 13, 2026 03:52
@gpshead
gpshead merged commit 48f18f0 into python:main Oct 13, 2026
55 checks passed

Uh oh!

There was an error while loading. Please reload this page.

@bedevere-app bedevere-app Bot removed the awaiting merge label Oct 13, 2026
@gpshead
gpshead deleted the marshal-exact-refs branch October 13, 2026 04:22
@bedevere-app

bedevere-app Bot commented Oct 13, 2026

Copy link
Copy Markdown

GH-159290 is a backport of this pull request to the 3.15 branch.

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@khmyznikov khmyznikov mentioned this pull request Oct 14, 2026
Pip install performance improvements on Windows pypa/pip#14286
Draft
4 tasks done
gpshead added a commit to gpshead/cpython that referenced this pull request Oct 24, 2026
@gpshead
[3.15] pythongh-129724: pythongh-156504: Make marshal output independ…
8fb8fc3
…ent of reference counts (pythonGH-159192)

The marshal writer decided whether an object can be referenced later
(FLAG_REF) from its reference count, so the output for the same value
depended on what else in the process held a reference to its parts, and
differed between the default and the free-threaded builds' view of the
same objects.  This is the remaining cause of pythongh-129724 and the cause of
pythongh-156504.

FLAG_REF is now set exactly on the objects which are written more than
once in the dump.  The writer still makes one pass: it records the
position of the type byte of each object it remembers and of each
TYPE_REF, and when the dump is complete it sets the flags and renumbers
the references.  A reference count of one is only used to skip the
hashtable.  Interned strings are no longer flagged unconditionally.

The reader is unchanged and the format version is not bumped: both the
old and the new output are loaded by old and new readers.

PyMarshal_WriteObjectToFile() now builds the data in memory and writes
it with one fwrite(), since the flags are only known at the end.
Nothing is written if the object cannot be marshalled.

(cherry picked from commit 48f18f0)
gpshead added a commit that referenced this pull request Oct 24, 2026
@gpshead
gh-129724: improve the news entry wording for gh-159192 and gh-159193 (…
081f6fb
…gh-160628)

improve the news entry wording for gh-159192 and gh-159193
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Reviewers

@methane methane methane approved these changes
@ericsnowcurrently ericsnowcurrently Awaiting requested review from ericsnowcurrently ericsnowcurrently is a code owner
@ZeroIntensity ZeroIntensity Awaiting requested review from ZeroIntensity ZeroIntensity is a code owner
@serhiy-storchaka serhiy-storchaka Awaiting requested review from serhiy-storchaka

Uh oh!

There was an error while loading. Please reload this page.

Assignees

@gpshead gpshead

Labels

stdlib Standard Library Python modules in the Lib/ directory type-bug An unexpected behavior, bug, or error

Projects

None yet

Milestone

No milestone

Development

Successfully merging this pull request may close these issues.

Concurrent compilation changes marshal output in free-threaded builds

Uh oh!

There was an error while loading. Please reload this page.

3 participants

@gpshead @bedevere-bot @methane
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.
You are visitor 01402503 — GeezerVison does not store the pages you look up.
Best viewed with Netscape Navigator 4.7 or Internet Explorer 6 at 800×600. No cookies required.