-
-
Notifications
You must be signed in to change notification settings - Fork 32.2k
gh-129107: make bytearray free-thread safe #129108
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'll yield to someone else, but this is one of the things that we probably shouldn't do with critical sections. bytearray
is a hot object--we should keep performance in mind here. A lot (but not all!) of bytearray
isn't prone to deadlocks because it doesn't typically re-enter.
I'm still undecided, but it might be worth borrowing from the FT list implementation for doing it locklessly.
In this I was just going from what I saw in Line 330 in e65a1eb
Though optimizations are of course on the table, first pass is just to get it correct and fix the crashes in the script in the issue for this PR. EDIT: Though list does store whole objects and this stores bytes, so yeah, would be good to hear from someone with domain knowledge here. EDIT: You made me curious so here are some very rough totally unscientific timings to compare (note is VM). TL;DR:
Main branch gil disabled: $ ./python
Python 3.14.0a4+ experimental free-threading build (heads/main:f3980af38b, Jan 20 2025, 22:11:16) [GCC 11.4.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import sysconfig; sysconfig.get_config_var('CONFIG_ARGS')
"'--enable-optimizations' '--with-lto' '--disable-gil'"
>>> from timeit import timeit
>>> b = bytearray(b'0')
>>> timeit('b[0]', number=1000000000, globals=globals())
11.69659708800009
>>> timeit('b[0]', number=1000000000, globals=globals())
11.63345341299987
>>> timeit('b[0]', number=1000000000, globals=globals())
11.640422253999986 This PR gil disabled: $ ./python
Python 3.14.0a4+ experimental free-threading build (heads/fix-issue-129107:832cc05259, Jan 20 2025, 22:18:24) [GCC 11.4.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import sysconfig; sysconfig.get_config_var('CONFIG_ARGS')
"'--enable-optimizations' '--with-lto' '--disable-gil'"
>>> from timeit import timeit
>>> b = bytearray(b'0')
>>> timeit('b[0]', number=1000000000, globals=globals())
14.824142644999938
>>> timeit('b[0]', number=1000000000, globals=globals())
14.943688319000103
>>> timeit('b[0]', number=1000000000, globals=globals())
14.88797294799997 Main branch gil enabled: $ ./python
Python 3.14.0a4+ (heads/main:f3980af38b, Jan 21 2025, 06:14:12) [GCC 11.4.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import sysconfig; sysconfig.get_config_var('CONFIG_ARGS')
"'--enable-optimizations' '--with-lto'"
>>> from timeit import timeit
>>> b = bytearray(b'0')
>>> timeit('b[0]', number=1000000000, globals=globals())
10.562217106999924
>>> timeit('b[0]', number=1000000000, globals=globals())
10.573908387000074
>>> timeit('b[0]', number=1000000000, globals=globals())
10.467421004000016 This PR gil enabled: $ ./python
Python 3.14.0a4+ (heads/fix-issue-129107:832cc05259, Jan 21 2025, 06:35:31) [GCC 11.4.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import sysconfig; sysconfig.get_config_var('CONFIG_ARGS')
"'--enable-optimizations' '--with-lto'"
>>> from timeit import timeit
>>> b = bytearray(b'0')
>>> timeit('b[0]', number=1000000000, globals=globals())
10.491293710999798
>>> timeit('b[0]', number=1000000000, globals=globals())
10.446074409000175
>>> timeit('b[0]', number=1000000000, globals=globals())
10.451200100999813 And just for a sanity check, my system vanilla py 3.10: $ python
Python 3.10.12 (main, Nov 6 2024, 20:22:13) [GCC 11.4.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import sysconfig; sysconfig.get_config_var('CONFIG_ARGS')
"'--enable-shared' '--prefix=/usr' '--libdir=/usr/lib/x86_64-linux-gnu' '--enable-ipv6' '--enable-loadable-sqlite-extensions' '--with-dbmliborder=bdb:gdbm' '--with-computed-gotos' '--without-ensurepip' '--with-system-expat' '--with-dtrace' '--with-system-libmpdec' '--with-wheel-pkg-dir=/usr/share/python-wheels/' 'MKDIR_P=/bin/mkdir -p' '--with-system-ffi' 'CC=x86_64-linux-gnu-gcc' 'CFLAGS=-g -fstack-protector-strong -Wformat -Werror=format-security ' 'LDFLAGS=-Wl,-Bsymbolic-functions -g -fwrapv -O2 ' 'CPPFLAGS=-Wdate-time -D_FORTIFY_SOURCE=2'"
>>> from timeit import timeit
>>> b = bytearray(b'0')
>>> timeit('b[0]', number=1000000000, globals=globals())
18.547491659999878
>>> timeit('b[0]', number=1000000000, globals=globals())
18.55064152900013
>>> timeit('b[0]', number=1000000000, globals=globals())
18.568128662999925 |
I suggest to split this PR into 2:
|
Makes sense, iterator is smaller (and easier to backport?) so will break that out and send it separate up later today and leave this one as the main bytearray? |
Yup, SGTM, ping me for reviews. |
@colesbury This PR fixes thread safety of bytearray with critical section; do we want to have lock free reads for this too? |
No, probably not. At least not for now. |
Thank you very much for the continued efforts 🙇 Are there plans to backport this (and the follow-up PRs) to 3.13? |
As of now there are no plans to backport these. |
Most of the work has been wrapping functions that need it in critical sections (one at a time, testing each one). There were some functions which were pointed directly at stringlib methods and needed critical sections, but since stringlib is shared with
str
andbytes
and those immutable types don't need locks, the stringlib functions were not touched. Instead, they were wrapped in functions which take object locks.There is work left to do, testing and doing the iterator. But starting as it is, this PR passes the test suite (at least on my system and build) as well as fixing the error cases presented in the script in the associated issue.
Functions which were looked at ("
-
" means no critical section was needed, otherwise function was fixed in one way or another):Comments and guidance requested.