Repository navigation
Python3.13 performance Issue with python.org macOS installers on ARM Macs #122580
Description
Bug description:
Python3.13rc1 seems to be about 20-25% slower than Python3.12.4.
Platform: M1 Mac running MacOS Sonoma 14.5
I first noticed the issue when running this repository where Python3.13rc1 ran on average 20-25% slower than Python3.12.4
With help from the Python discord server, we were able to create this minimal reproducible example:
from time import process_time
class Lexer:
def __init__(self, data):
self.data = data
self.index = 0
def detect(self, text):
i = 0
for char in text:
if self.peek(i) != char:
return False
i += 1
return True
def peek(self, amt=1):
if self.index + amt < len(self.data):
return self.data[self.index + amt]
else:
return None
text = "return text + indent * ' ' + '}'\n" * 200
needle = "return text + indent * ' ' + '}'\n" * 199 + "Nope\n"
lexer = Lexer(text)
start = process_time()
for x in range(10_000):
lexer.detect(needle)
print(process_time() - start)These were the results across many tests:
3.12.4: 7.742122s
3.13rc1: 8.309326s
The performance issue was not reproducible on other Operating Systems.
CPython versions tested on:
3.11.1, 3.12.4, 3.13
CPython Options:
For my testing, I used the official Python3.13rc1 installer from Python.org, which has the following configuration:
OPT:
3.13rc1: -DNDEBUG -g -O3 -Wall
3.12.4: -DNDEBUG -g -O3 -Wall
CONFIG_ARGS:
3.13rc1: '--enable-framework' '--with-framework-name=Python' '--enable-universalsdk=/' '--with-universal-archs=universal2' '--enable-optimizations' '--with-lto' '--without-ensurepip' '--with-system-libmpdec' '--with-openssl=/Users/nad/release-tools/macos-installer/installer/variant/binaries/build/libraries/usr/local' 'LIBLZMA_CFLAGS=-I/Users/nad/release-tools/macos-installer/installer/variant/binaries/build/libraries/usr/local/include' 'LIBLZMA_LIBS=-L/Users/nad/release-tools/macos-installer/installer/variant/binaries/build/libraries/usr/local/lib -llzma' 'LIBMPDEC_CFLAGS=-I/Users/nad/release-tools/macos-installer/installer/variant/binaries/build/libraries/usr/local/include' 'LIBMPDEC_LIBS=-L/Users/nad/release-tools/macos-installer/installer/variant/binaries/build/libraries/usr/local/lib -lmpdec -lm' 'LIBSQLITE3_CFLAGS=-I/Users/nad/release-tools/macos-installer/installer/variant/binaries/build/libraries/usr/local/include' 'LIBSQLITE3_LIBS=-L/Users/nad/release-tools/macos-installer/installer/variant/binaries/build/libraries/usr/local/lib -lsqlite3' 'TCLTK_CFLAGS=-I/Users/nad/release-tools/macos-installer/installer/variant/binaries/build/libraries/usr/local/include' 'TCLTK_LIBS=-L/Users/nad/release-tools/macos-installer/installer/variant/binaries/build/libraries/usr/local/lib -ltcl -ltk' 'CC=clang'
3.12.4: '-C' '--enable-framework' '--enable-universalsdk=/' '--with-universal-archs=universal2' '--with-computed-gotos' '--without-ensurepip' '--with-openssl=/tmp/_py/libraries/usr/local' '--enable-optimizations' '--with-lto' 'TCLTK_CFLAGS=-I/tmp/_py/libraries/usr/local/include' 'TCLTK_LIBS=-ltcl8.6 -ltk8.6' 'LDFLAGS=-g' 'CFLAGS=-g' 'CC=clang'
Operating systems tested on:
Linux, macOS, Windows
Metadata
Metadata
Assignees
Labels
Projects
- StatusShow more project fieldsDone
Activity
ZeroIntensity commentedon Aug 1, 2024
Me and @devdanzin have figured out that this is only on ARM, Windows and Linux x86 seemed to be faster based on the benchmarks.
mdboom commentedon Aug 1, 2024
Thanks for this report. Across a broad range of benchmarks measuring on the Faster CPython benchmarking machine, we see between a 3% and 8% speedup for 3.13.0b4 vs. 3.12.0 on a MacOS ARM machine.
That doesn't mean that this particular example hasn't found a narrow regression case. In our own benchmarking, some specific benchmarks are in fact slower, but only
telcois that much slower (which is a known regression in the decimal module). Maybe some line profiling of 3.12 vs. 3.13 would be helpful here, or some pystats results.I also noticed that you have
--with-computed-gotosfor 3.12, but not 3.13 here. That may not be relevant since it's on by default anyway (and I'm not sure it even applies for clang), but we should make sure all other variables are equal.mdboom commentedon Aug 1, 2024
I just tried to reproduce the above result on our benchmarking M1 Mac, also running macOS 14.5, and got different results:
I suspect this comes down to different configurations between the builds. It looks like 3.12.0 on your machine was built on a different machine? Try building each in exactly the same environment and see if you can still reproduce.
hg0428 commentedon Aug 1, 2024
I am using 3.12.4, not 3.12.0.
I have tested 3.11, 3.12.1, 3.12.3, all from Python.org at different points in time.
I have also tested many different betas of 3.13, and the results have been consistent.
All the versions installed on my machine are the default ones from Python.org.
brandtbucher commentedon Aug 1, 2024
I also can't reproduce with local optimized builds on an M2 Pro running Sonoma 14.5 (3 runs each):
However, I can reproduce with the python.org downloads:
So the issue seems to be with the releases themselves. @ned-deily, anything different with macOS releases this time around?
brandtbucher commentedon Aug 1, 2024
So, this is kinda strange. Consider this reproducer:
With the 3.12.4 python.org download, this prints:
On 3.13.0rc1:
So the loop over the string is faster at module level on 3.13, but faster at function level in 3.12. And 3.13 is faster on both if I replace
"X" * 100_000_000withrange(100_000_000).brandtbucher commentedon Aug 1, 2024
The issue could be some sort of messed-up (or skipped) PGO with the release build. The global version has lots of
FOR_ITER -> STORE_NAMEpairs while the function version has lots ofFOR_ITER -> STORE_FASTpairs. That's really the only difference that I can see. I'd expect PGO to make the latter very fast, at the expense of the former.But at this point I'm just guessing.
mdboom commentedon Aug 1, 2024
Just dittoing this. I agree, there may be something that changed in the build environment between releases.
ned-deily commentedon Aug 1, 2024
I can reproduce a similar slower performance between the python.org 3.12.4 and 3.13.0rc1 builds on an M3 MacBook Pro with macOS 14.6. However, using the same python.org installers on an Intel i9 iMac also with macOS 14.6 shows a performance improvement between 3.12.4 and 3.13.0rc1. These are both universal2 builds meaning they should be running native arm or x86_64 code on each. And, interestingly, if I force running the universal2 builds in x86_64 mode using Rosetta2 emulation on the same M3 (by using python3.1x-intel64), I see performance differences similar to running natively on the Intel Mac, that is 3.13.0rc1 is somewhat faster.
There are a number of differences in the build environments used for 3.12.4 and for 3.13.0rc1, including the compiler versions and deployment targets. I agree that something with optimizations seems a likely culprit. I will dig into it and report back.
Thanks for the report and the simple reproducer, @hg0428!
ned-deily commentedon Aug 2, 2024
A quick update: I've run the test case on the full range of python.org macOS installers from 3.13.0a5 through 3.13.0rc1 on both Apple Silicon (M3, arm64) and Intel (i9) Macs and it appears that rc1 on Apple Silicon is the outlier. I also ran the test case on all of the macOS installers where we also provided a free-threading build, that is, 3.13.0b2 through 3.13.0rc1, again, on both CPU types; there, the test-case performance differences roughly mirror those of the the normal (GIL) builds except that the rc1 free-threading version does not exhibit the performance degradation that the normal version does on Apple Silicon. So, the good news at this point is that this particular performance issue appears to be isolated but there is more work to be done to pin it down further. More tomorrow.
20 remaining items
ned-deily commentedon Sep 3, 2024
Extensive testing with the microbenchmarks provided in this issue and in #122832 confirm that (1) there was a significant performance degradation for at least some test cases with the 3.13.0rc1 provided by the python.org installer particularly on Apple Silicon Macs and that (2) that performance degradation appears to be due to the use of the ThinLTO (Link Time Optimization) Python build option (
--with-ltodefaulting to--with-lto=thin) with recent Apple-supplied LLVM/Clang build tools. As noted earlier in this issue, the degradation was seen with very simple builds of the test case(s) when--with-ltowas included. It was subsequently determined through extensive testing that using the original monolithic LTO build option (--with-lto=full) provides a substantial performance boost for these two benchmarks and, in all test configurations shown here, the three build configurations using--with-lto=fullalways provided the best performance with these microbenchmarks, in some cases, dramatically better.Since performance can vary greatly due to many factors, it is unreasonable to expect the same level of improvement in more extensive real-world benchmarks but it seems likely that an overall improvement of some magnitude may be seen with most benchmarks. From reading a bit about ThinLTO, it seems one of its biggest advantages was expected to be providing faster incremental builds rather than doing the monolithic optimization for each build. For the python.org installer case, that isn't an advantage since we always build the binary components of the installer from scratch for each release. And the documentation implies that, in most cases, the monolithic LTO would be expected to provide better run-time performance anyway. So all of that seems to indicate that full LTO is the right choice for the installer build in any case.
Moving forward, with the imminent release of 3.13.0rc2, we will use
--with-lto=fullto build the macOS installer for it and will run these microbenchmarks again before release. It would be great if more extensive benchmarks could be run with the released installer and results reported here to this issue. I will leave it open for that purpose for the immediate future.While there are still unanswered questions about exactly what it is about ThinLTO that causes the performance degradation in these environments, I am not planning to delve deeper into it since it's not something I have particular expertise in and it seems that using full LTO is a perfectly satisfactory solution for the macOS installer. Feel free to dive in yourself!
Thanks again to everyone who has helped out with reporting and investigation this!
Appended here is a summary of the more pertinent test results while investigating this issue:
Test Cases
test_122580is the test case provided above in this issue.test_122832is the var_access_benchmark referenced in Significant performance degradation in the macOS build #122832._Apple Silicon (MacBook Pro 14-inch, Nov 2023, M3, 16GB)
Summary
test_122580 (version = 3.12.4)test_122580 (version = 3.12.5)test_122580 (version = 3.13.0b1)test_122580 (version = 3.13.0b4)test_122580 (version = 3.13.0rc1)test_122580 (version = 3.13.0rc1-no-lto)test_122580 (version = 3.13.0rc1-lto-full)test_122580 (version = 3.13.0rc1+-xcode)test_122580 (version = 3.13.0rc1+-xcode-beta)test_122832 (version = 3.12.4)test_122832 (version = 3.12.5)test_122832 (version = 3.13.0b1)test_122832 (version = 3.13.0b4)test_122832 (version = 3.13.0rc1)test_122832 (version = 3.13.0rc1-no-lto)test_122832 (version = 3.13.0rc1-lto-full)test_122832 (version = 3.13.0rc1+-xcode)test_122832 (version = 3.13.0rc1+-xcode-beta)Intel (iMac Retina 5K 27-inch, 2019, 3.6 GHz 8-Core Intel Core i9, 40GB)
Summary
test_122580 (version = 3.12.4)test_122580 (version = 3.12.5)test_122580 (version = 3.13.0b1)test_122580 (version = 3.13.0b4)test_122580 (version = 3.13.0rc1)test_122580 (version = 3.13.0rc1-no-lto)test_122580 (version = 3.13.0rc1-lto-full)test_122580 (version = 3.13.0rc1+-xcode)test_122580 (version = 3.13.0rc1+-xcode-beta)test_122832 (version = 3.12.4)test_122832 (version = 3.12.5)test_122832 (version = 3.13.0b1)test_122832 (version = 3.13.0b4)test_122832 (version = 3.13.0rc1)test_122832 (version = 3.13.0rc1-no-lto)test_122832 (version = 3.13.0rc1-lto-full)test_122832 (version = 3.13.0rc1+-xcode)test_122832 (version = 3.13.0rc1+-xcode-beta)Build configurations
mdboom commentedon Sep 3, 2024
Thanks for all your hard work on this, @ned-deily. This finding seems reasonable. I've put the rc2 release date on my calendar and will run it against the full pyperformance suite then to confirm.
brandtbucher commentedon Sep 3, 2024
Thanks @ned-deily. I wonder, should the default for
--with-ltobefullinstead ofthin? I just assumed that was already the case (and I'm not sure what our benchmarking infrastructure assumes).In general, if I'm passing
--with-lto, I'm expecting the build to take a while (and be as fast as possible in return).itamaro commentedon Sep 3, 2024
What's the default LTO (full or thin) for the Linux builds?
mdboom commentedon Sep 3, 2024
IIUC, thin LTO is only supported on llvm/clang, so our gcc-based Linux builds don't use it.
ned-deily commentedon Sep 3, 2024
The default for
--with-ltois platform-dependent and compiler-dependent.configure.achas a bunch of code to deal with it (see, for example, starting here). There has also been a fair amount of discussion aboutltooptions (for example, here and here). If somebody wants to revisit that, it would be better to do it in another issue; I'd like to keep this focused on the macOS installer.[-]Python3.13 Performance Issue on MacOS ARM[/-][+]Python3.13 performance Issue with python.org macOS installers on ARM Macs[/+]rhettinger commentedon Sep 7, 2024
Just tested 3.13rc2 and the performance issues are solved. Thank you.
mdboom commentedon Sep 9, 2024
Same (on our usual macOS lab machine).
itamaro commentedon Sep 9, 2024
closing as fixed, thanks for testing @rhettinger & @mdboom !
please re-open if there's anything left to do here.
ned-deily commentedon Oct 1, 2024
FWIW, 3.13.0rc3 differences noted: #124567 (comment)