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 — Performance regression for making bound methods · Issue #83298 · python/cpython

Performance regression for making bound methods #83298

New issue
Copy link
New issue
Copy link
Open
Open
Performance regression for making bound methods#83298
Copy link
Labels
3.10 (EOL)end of lifeend of life3.11only security fixesonly security fixes3.12only security fixesonly security fixes3.9 (EOL)end of lifeend of lifeinterpreter-core(Objects, Python, Grammar, and Parser dirs)(Objects, Python, Grammar, and Parser dirs)performancePerformance or resource usagePerformance or resource usage

Description

@rhettinger
rhettinger
opened on Dec 21, 2019
Issue body actions
BPO 39117
Nosy @rhettinger, @vstinner, @benjaminp, @methane, @serhiy-storchaka, @pablogsal

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 = None
closed_at = None
created_at = <Date 2019-12-21.20:13:01.396>
labels = ['interpreter-core', '3.9', 'performance']
title = 'Performance regression for making bound methods'
updated_at = <Date 2020-04-28.21:51:46.108>
user = 'https://github.com/rhettinger'

bugs.python.org fields:

activity = <Date 2020-04-28.21:51:46.108>
actor = 'vstinner'
assignee = 'none'
closed = False
closed_date = None
closer = None
components = ['Interpreter Core']
creation = <Date 2019-12-21.20:13:01.396>
creator = 'rhettinger'
dependencies = []
files = []
hgrepos = []
issue_num = 39117
keywords = []
message_count = 7.0
messages = ['358781', '358802', '358807', '367556', '367566', '367567', '367568']
nosy_count = 6.0
nosy_names = ['rhettinger', 'vstinner', 'benjamin.peterson', 'methane', 'serhiy.storchaka', 'pablogsal']
pr_nums = []
priority = 'normal'
resolution = None
stage = None
status = 'open'
superseder = None
type = 'performance'
url = 'https://bugs.python.org/issue39117'
versions = ['Python 3.9']

Activity

  1. rhettinger commented on Dec 21, 2019

    @rhettinger
    rhettinger
    on Dec 21, 2019
    ContributorAuthor
    More actions
    $ python3.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'
    1000000 loops, best of 11: 230 nsec per loop
    
    $ python3.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'
    2000000 loops, best of 11: 149 nsec per loop
    
    $ python3.7 -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: 159 nsec per loop
    
    $ python3.6 -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'
    10000000 loops, best of 11: 0.159 usec per loop

    Timings made using the recent released python.org macOS 64-bit builds.

  2. rhettinger
    added
    3.9 (EOL)end of lifeend of life
    interpreter-core(Objects, Python, Grammar, and Parser dirs)(Objects, Python, Grammar, and Parser dirs)
    performancePerformance or resource usagePerformance or resource usage
    on Dec 21, 2019
  3. benjaminp commented on Dec 23, 2019

    @benjaminp
    benjaminp
    on Dec 23, 2019
    Contributor
    More actions

    Probably bpo-37340

  4. methane commented on Dec 23, 2019

    @methane
    methane
    on Dec 23, 2019
    Member
    More actions

    Is this regression is large enough to revive the free_list for bound methods?

  5. rhettinger commented on Apr 28, 2020

    @rhettinger
    rhettinger
    on Apr 28, 2020
    ContributorAuthor
    More actions

    This performance regression in still present in 3.9.0a6

    Results from Tools/scripts/var_access_benchmark.py:

                         Python 3.8.2       Python 3.9.0a6
                         ------------       \--------------
    

    read_boundmethod 27.7 ns 47.1 ns

    Is this regression is large enough to revive the free_list
    for bound methods?

    Yes!

  6. vstinner commented on Apr 28, 2020

    @vstinner
    vstinner
    on Apr 28, 2020
    Member
    More actions

    read_boundmethod 27.7 ns 47.1 ns

    Extract of Tools/scripts/var_access_benchmark.py:

    def read_boundmethod(trials=trials, a=A()):
        for t in trials:
            a.m;    a.m;    a.m;    a.m;    a.m
            a.m;    a.m;    a.m;    a.m;    a.m
            a.m;    a.m;    a.m;    a.m;    a.m
            a.m;    a.m;    a.m;    a.m;    a.m
            a.m;    a.m;    a.m;    a.m;    a.m

    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?

  7. vstinner commented on Apr 28, 2020

    @vstinner
    vstinner
    on Apr 28, 2020
    Member
    More actions

    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.

  8. vstinner commented on Apr 28, 2020

    @vstinner
    vstinner
    on Apr 28, 2020
    Member
    More actions

    By the way, in sorted(data, key=str.upper): str.upper is an unbound method, no?

    $ ./python
    Python 3.9.0a6+ (heads/master:d9a43e20fa, Apr 28 2020, 23:50:37) 
    >>> type(str.upper)
    <class 'method_descriptor'>
  9. ezio-melotti
    transferred this issue fromon Apr 10, 2022
  10. iritkatriel
    added
    3.11only security fixesonly security fixes
    3.10 (EOL)end of lifeend of life
    3.12only security fixesonly security fixes
    on Sep 12, 2022
  11. eendebakpt commented on Feb 1, 2026

    @eendebakpt
    eendebakpt
    on Feb 1, 2026
    Contributor
    More actions

    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
    
  12. eendebakpt commented on Jun 19, 2026

    @eendebakpt
    eendebakpt
    on Jun 19, 2026
    Contributor
    More actions

    I investigated a bit more:

    • Part of the slowdown is due to immortal objects (pep 683), but this only explains a small part
    • Freelists have been removed and added later. On PGO builds the effect is also minor
    • The method access a.m is indeed slower. There are some changes that made it slower (but these changes made other paths faster):
    1. Due to managed dicts we have one more pointer indirection
    2. The LOAD_ATTR AND LOAD_METHOD have been merged (Merge LOAD_METHOD back into LOAD_ATTR for better specialization #93429) increasing inline cache and some more handling

    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).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    3.10 (EOL)end of lifeend of life3.11only security fixesonly security fixes3.12only security fixesonly security fixes3.9 (EOL)end of lifeend of lifeinterpreter-core(Objects, Python, Grammar, and Parser dirs)(Objects, Python, Grammar, and Parser dirs)performancePerformance or resource usagePerformance or resource usage

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions

      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.