You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Performance regression for making bound methods #83298
Note: these values reflect the state of the issue at the time it was migrated and might not reflect the current state.
Show more details
GitHub fields:
assignee=Noneclosed_at=Nonecreated_at=<Date2019-12-21.20:13:01.396>labels= ['interpreter-core', '3.9', 'performance']
title='Performance regression for making bound methods'updated_at=<Date2020-04-28.21:51:46.108>user='https://github.com/rhettinger'
Which kind of code pattern is impacted by this performance regression, apart this micro-benchmark? Do you notice a significant slowdown in pyperformance?
I compared sorted(data, key=str.upper) performance between before the removal of the free list (parent of commit 3e54b57) and the current master branch, I get:
Mean +- std dev: [master] 167 us +- 4 us -> [cache] 179 us +- 7 us: 1.07x slower (+7%)
I cannot reproduce the "70% slowdown".
Raymond: How did you compile Python? What is your OS? How did you run your benchmark?
---
It's a quick & dirty benchmark run. I didn't use LTO+PGO and I didn't isolate my CPU. Maybe the difference of 12 ns is just caused by the noise of my coarse benchmark procedure.
I used commands:
# master
git checkout master
git clean -fdx
./configure && make && ./python -m venv env && env/bin/python -m pip install pyperf
env/bin/python -m pyperf timeit -s 'import random; data = [f"x{i}" for i in range(1000)]; random.shuffle(data)' 'sorted(data, key=str.upper)' -o ../master.json
# before removal of the free list
git checkout 3e54b57^
git clean -fdx
./configure && make && ./python -m venv env && env/bin/python -m pip install pyperf
env/bin/python -m pyperf timeit -s 'import random; data = [f"x{i}" for i in range(1000)]; random.shuffle(data)' 'sorted(data, key=str.upper)' -o ../master.json
That's a Fedora 31 using GCC -O3 (GCC 9.3.1).
--
The commit before the removal of the free list is:
The performance regression became a bit worse in 3.11 and improved a bit again in 3.14 (maybe because a freelist was added again #128692, although I did not measure).
(fiftyone) c:\projects\tmp>uv run -p 3.8 -m timeit -r 11 -s "class A: pass" -s "A.m = lambda s: None" -s "a = A()" "a.m; a.m; a.m; a.m; a.m"
5000000 loops, best of 11: 62 nsec per loop
(fiftyone) c:\projects\tmp>uv run -p 3.9 -m timeit -r 11 -s "class A: pass" -s "A.m = lambda s: None" -s "a = A()" "a.m; a.m; a.m; a.m; a.m"
5000000 loops, best of 11: 86.3 nsec per loop
(fiftyone) c:\projects\tmp>uv run -p 3.10 -m timeit -r 11 -s "class A: pass" -s "A.m = lambda s: None" -s "a = A()" "a.m; a.m; a.m; a.m; a.m"
5000000 loops, best of 11: 84.7 nsec per loop
(fiftyone) c:\projects\tmp>uv run -p 3.11 -m timeit -r 11 -s "class A: pass" -s "A.m = lambda s: None" -s "a = A()" "a.m; a.m; a.m; a.m; a.m"
2000000 loops, best of 11: 105 nsec per loop
(fiftyone) c:\projects\tmp>uv run -p 3.12 -m timeit -r 11 -s "class A: pass" -s "A.m = lambda s: None" -s "a = A()" "a.m; a.m; a.m; a.m; a.m"
2000000 loops, best of 11: 107 nsec per loop
(fiftyone) c:\projects\tmp>uv run -p 3.13 -m timeit -r 11 -s "class A: pass" -s "A.m = lambda s: None" -s "a = A()" "a.m; a.m; a.m; a.m; a.m"
2000000 loops, best of 11: 117 nsec per loop
(fiftyone) c:\projects\tmp>uv run -p 3.14 -m timeit -r 11 -s "class A: pass" -s "A.m = lambda s: None" -s "a = A()" "a.m; a.m; a.m; a.m; a.m"
2000000 loops, best of 11: 103 nsec per loop
For normal attributes the result is different:
(fiftyone) c:\projects\tmp>uv run -p 3.8 -m timeit -r 11 -s "class A: pass" -s "A.m = 10" -s "a = A()" "a.m; a.m; a.m; a.m; a.m"
10000000 loops, best of 11: 37.6 nsec per loop
(fiftyone) c:\projects\tmp>uv run -p 3.9 -m timeit -r 11 -s "class A: pass" -s "A.m = 10" -s "a = A()" "a.m; a.m; a.m; a.m; a.m"
10000000 loops, best of 11: 38.1 nsec per loop
(fiftyone) c:\projects\tmp>uv run -p 3.10 -m timeit -r 11 -s "class A: pass" -s "A.m = 10" -s "a = A()" "a.m; a.m; a.m; a.m; a.m"
5000000 loops, best of 11: 42.3 nsec per loop
(fiftyone) c:\projects\tmp>uv run -p 3.11 -m timeit -r 11 -s "class A: pass" -s "A.m = 10" -s "a = A()" "a.m; a.m; a.m; a.m; a.m"
5000000 loops, best of 11: 52 nsec per loop
(fiftyone) c:\projects\tmp>uv run -p 3.12 -m timeit -r 11 -s "class A: pass" -s "A.m = 10" -s "a = A()" "a.m; a.m; a.m; a.m; a.m"
5000000 loops, best of 11: 49.5 nsec per loop
(fiftyone) c:\projects\tmp>uv run -p 3.13 -m timeit -r 11 -s "class A: pass" -s "A.m = 10" -s "a = A()" "a.m; a.m; a.m; a.m; a.m"
10000000 loops, best of 11: 28.8 nsec per loop
(fiftyone) c:\projects\tmp>uv run -p 3.14 -m timeit -r 11 -s "class A: pass" -s "A.m = 10" -s "a = A()" "a.m; a.m; a.m; a.m; a.m"
10000000 loops, best of 11: 24.7 nsec per loop
We can restore performance with a dedicated opcode. For a microbenchmark (as in the OP) a.m is about 1.34x faster. This does require extra tier1 opcodes though (or tier2 only, but that pending on a possible jit pep).
Activity
rhettinger commentedon Dec 21, 2019
Timings made using the recent released python.org macOS 64-bit builds.
benjaminp commentedon Dec 23, 2019
Probably bpo-37340
methane commentedon Dec 23, 2019
Is this regression is large enough to revive the free_list for bound methods?
rhettinger commentedon Apr 28, 2020
This performance regression in still present in 3.9.0a6
Results from Tools/scripts/var_access_benchmark.py:
read_boundmethod 27.7 ns 47.1 ns
Yes!
vstinner commentedon Apr 28, 2020
Extract of Tools/scripts/var_access_benchmark.py:
Which kind of code pattern is impacted by this performance regression, apart this micro-benchmark? Do you notice a significant slowdown in pyperformance?
When pyperformance was run before the change was merged, there was no significant difference:
https://bugs.python.org/issue37340#msg348425
In bpo-37340, you wrote that sorted(data, key=str.upper) is 70% slower. Would you mind to provide the benchmark?
vstinner commentedon Apr 28, 2020
I compared sorted(data, key=str.upper) performance between before the removal of the free list (parent of commit 3e54b57) and the current master branch, I get:
Mean +- std dev: [master] 167 us +- 4 us -> [cache] 179 us +- 7 us: 1.07x slower (+7%)
I cannot reproduce the "70% slowdown".
Raymond: How did you compile Python? What is your OS? How did you run your benchmark?
---
It's a quick & dirty benchmark run. I didn't use LTO+PGO and I didn't isolate my CPU. Maybe the difference of 12 ns is just caused by the noise of my coarse benchmark procedure.
I used commands:
# master
git checkout master
git clean -fdx
./configure && make && ./python -m venv env && env/bin/python -m pip install pyperf
env/bin/python -m pyperf timeit -s 'import random; data = [f"x{i}" for i in range(1000)]; random.shuffle(data)' 'sorted(data, key=str.upper)' -o ../master.json
# before removal of the free list
git checkout 3e54b57^
git clean -fdx
./configure && make && ./python -m venv env && env/bin/python -m pip install pyperf
env/bin/python -m pyperf timeit -s 'import random; data = [f"x{i}" for i in range(1000)]; random.shuffle(data)' 'sorted(data, key=str.upper)' -o ../master.json
That's a Fedora 31 using GCC -O3 (GCC 9.3.1).
--
The commit before the removal of the free list is:
commit 76b6451 (HEAD)
Date: Fri Jul 26 03:30:33 2019 +0200
Well, maybe I did a mistake.
vstinner commentedon Apr 28, 2020
By the way, in sorted(data, key=str.upper): str.upper is an unbound method, no?
eendebakpt commentedon Feb 1, 2026
The performance regression became a bit worse in 3.11 and improved a bit again in 3.14 (maybe because a freelist was added again #128692, although I did not measure).
For normal attributes the result is different:
eendebakpt commentedon Jun 19, 2026
I investigated a bit more:
a.mis indeed slower. There are some changes that made it slower (but these changes made other paths faster):LOAD_METHODback intoLOAD_ATTRfor better specialization #93429) increasing inline cache and some more handlingWe can restore performance with a dedicated opcode. For a microbenchmark (as in the OP)
a.mis about 1.34x faster. This does require extra tier1 opcodes though (or tier2 only, but that pending on a possible jit pep).