Repository navigation
Free-threaded Python: 3.14t/3.15t wheels and a lock-free word cache - #39
Merged
Merged
Conversation
The free-threaded build doesn't support the limited API, so it gets a regular (non-abi3) extension. cffi 2.x marks the module as free-threading safe, so the GIL stays off. Release the cffi buffers of the memory maps explicitly before closing them. The cyclic GC of free-threaded builds keeps objects alive while finalizers run, so at shutdown Dawg.__del__ could not close its mmap (BufferError). Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
test_threads.py runs lookups from 8 threads with small caches, so entries are also evicted under contention, and compares them with serial results. Without the LFU cache lock it fails on 3.14t within a second. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
cibuildwheel builds cp314t and cp315t wheels next to the abi3 wheel; the smoke test checks that importing the extension keeps the GIL off. The test matrix runs 3.14t and 3.15t, and on those also runs every test in eight threads at once with pytest-run-parallel. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Gentoo's Python records no -O flag for extension builds (its ebuild passes the user's CFLAGS as CFLAGS_NODIST), so a source build of libbin outside Portage ran at -O0, about four times slower per lookup. Add -O2 unless the interpreter's flags or CFLAGS/CXXFLAGS already set a level, so builds that use -O3 keep it. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
LFU_Cache held one lock across every lookup, including the computation on a miss, so on free-threaded Python adding threads made lookups slower. GenerationalCache keeps a new and an old generation of word forms in two dicts: reads take no lock, and a race can at worst compute a word twice. With generations of 4096 it matches the LFU cache's hit rate on real text. Each cached word form also keeps its finished BinEntry tuples, made on first use, so a lookup() hit copies a list instead of building namedtuples, which do not scale across threads on 3.14t. Plain lookup() of real text: about 1.5x faster on one thread, 6.5x faster with eight threads on 3.14t. LFU_Cache is gone; nothing outside islenska used it, and its hits/misses counters went with it. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
cffi 2.0 is the first version that runs free-threaded CPython without the GIL. Add the Free Threading classifier, and list the cp314t and cp315t wheels in RELEASING.md (30 wheels in all) and the README. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
cibuildwheel fails only if a build selector matches nothing, so a missing cp315t (or any other) wheel would otherwise pass unnoticed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Threads creating their first instances at once could open BÍN twice or read BinErrata.conf twice. Also note the cache's memory per word form. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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.
What is the problem?
islenska didn't build on free-threaded Python (3.14t). Its word cache also held one lock across every lookup, so more threads made lookups slower.
What is the solution?
abi3. cffi 2.x keeps the GIL off.cp314tandcp315twheels.GenerationalCachereplacesLFU_Cache, which nothing outside islenska used.BinEntrytuples, so a hit only copies a list. Constructing namedtuples doesn't scale on 3.14t.BufferErrorat shutdown on free-threaded builds.-O2when the interpreter records no optimization level. Gentoo's Python records none, so libbin ran at-O0, 4× slower.How was this tested?
BinandGreynirBin, gives the same checksums asmain.pytest-run-parallel, and on GIL 3.14.test_threads.py: it fails within a second if the old cache's lock is removed.test_cache.py: cached results equal uncached ones.Bin.lookupon real text, in thousands of lookups/s:wheels.yml, which only runs on tags. It needs a dry run with a test tag such as2.0.1-test1.🤖 Generated with Claude Code