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 — Severe performance degradation for tracing under 3.11 · Issue #93516 · python/cpython

Severe performance degradation for tracing under 3.11 #93516

New issue
Copy link
New issue
Copy link
Open
Open
Severe performance degradation for tracing under 3.11#93516
Copy link
Assignees
markshannon
Labels
3.11only security fixesonly security fixesinterpreter-core(Objects, Python, Grammar, and Parser dirs)(Objects, Python, Grammar, and Parser dirs)type-bugAn unexpected behavior, bug, or errorAn unexpected behavior, bug, or error

Description

@nedbat
nedbat
opened on Jun 5, 2022
Issue body actions

Bug report

Coverage.py is seeing a significant increase in overhead for tracing code in 3.11 compared to 3.10: coveragepy/coveragepy#1287

As an example:

cov proj python3.10 python3.11 3.11 vs 3.10
none bug1339.py 0.184 s 0.142 s 76%
none bm_sudoku.py 10.789 s 9.901 s 91%
none bm_spectral_norm.py 14.305 s 9.185 s 64%
6.4.1 bug1339.py 0.450 s 0.854 s 189%
6.4.1 bm_sudoku.py 27.141 s 55.504 s 204%
6.4.1 bm_spectral_norm.py 36.793 s 67.970 s 184%

(This is the output of lab/benchmark.py.)

Your environment

  • CPython versions tested on: 3.10, 3.11
  • Operating system and architecture: MacOS, Intel

Linked PRs

  • GH-93516: match logic of codeobject.c in _co_firsttraceable calculation #108028
Reacted by Aleksander Vognild Burkow, Mike Fiedler and Fantom_sunday

Activity

Next
  1. nedbat
    added
    type-bugAn unexpected behavior, bug, or errorAn unexpected behavior, bug, or error
    on Jun 5, 2022
  2. nedbat commented on Jun 5, 2022

    @nedbat
    nedbat
    on Jun 5, 2022
    MemberAuthor
    More actions

    @pablogsal @markshannon

  3. pablogsal
    added
    release-blocker
    3.11only security fixesonly security fixes
    on Jun 5, 2022
  4. Fidget-Spinner commented on Jun 5, 2022

    @Fidget-Spinner
    Fidget-Spinner
    on Jun 5, 2022
    Member
    More actions

    Related issue where a user reported that code with cProfile slowed down 1.6x, and the only thing I could pinpoint was that tracing itself slowed down, not cProfile #93381.

  5. sweeneyde commented on Jun 6, 2022

    @sweeneyde
    sweeneyde
    on Jun 6, 2022 · Hidden as off-topic
    show commentMore actions
  6. sweeneyde commented on Jun 6, 2022

    @sweeneyde
    sweeneyde
    on Jun 6, 2022 · Hidden as off-topic
    show commentMore actions
  7. sweeneyde commented on Jun 6, 2022

    @sweeneyde
    sweeneyde
    on Jun 6, 2022
    Member
    More actions

    I now realize this is probably due to using the Python tracer rather than the C tracer, will try again.

  8. sweeneyde commented on Jun 6, 2022

    @sweeneyde
    sweeneyde
    on Jun 6, 2022
    Member
    More actions

    Okay, now with Code coverage for Python, version 6.4.1 with C extension.

    Script used:
    import coverage
    from time import perf_counter
    
    def fib(n):
        if n <= 1:
            return n
        else:
            return fib(n-1) + fib(n-2)
    
    t0 = perf_counter()
    cov = coverage.Coverage()
    cov.start()
    fib(35)
    cov.stop()
    cov.save()
    t1 = perf_counter()
    print(t1 - t0)
    Profile on 3.10 branch: took 13.3 seconds

    Functions taking more than 1% of CPU:

    Function Name Total CPU [unit, %] Self CPU [unit, %] Module
    | - _PyEval_EvalFrameDefault 12372 (99.78%) 3540 (28.55%) python310.dll
    | - [External Call] tracer.cp310-win_amd64.pyd 2769 (22.33%) 1474 (11.89%) tracer.cp310-win_amd64.pyd
    | - maybe_call_line_trace 3872 (31.23%) 1206 (9.73%) python310.dll
    | - _PyCode_CheckLineNumber 1168 (9.42%) 988 (7.97%) python310.dll
    | - call_trace 3431 (27.67%) 548 (4.42%) python310.dll
    | - PyLong_FromLong 370 (2.98%) 369 (2.98%) python310.dll
    | - _PyFrame_New_NoTrack 385 (3.11%) 338 (2.73%) python310.dll
    | - lookdict_unicode_nodummy 324 (2.61%) 324 (2.61%) python310.dll
    | - call_trace_protected 2107 (16.99%) 287 (2.31%) python310.dll
    | - frame_dealloc 270 (2.18%) 270 (2.18%) python310.dll
    | - set_add_entry 264 (2.13%) 264 (2.13%) python310.dll
    | - PyDict_GetItem 579 (4.67%) 255 (2.06%) python310.dll
    | - _PyEval_MakeFrameVector 633 (5.11%) 248 (2.00%) python310.dll
    | - _PyEval_Vector 12372 (99.78%) 196 (1.58%) python310.dll
    | - PyLineTable_NextAddressRange 180 (1.45%) 180 (1.45%) python310.dll
    | - call_function 12372 (99.78%) 169 (1.36%) python310.dll
    | - PyObject_RichCompare 457 (3.69%) 169 (1.36%) python310.dll
    | - binary_op1 486 (3.92%) 157 (1.27%) python310.dll
    | - long_richcompare 207 (1.67%) 157 (1.27%) python310.dll
    Profile on main branch: took 23.0 seconds

    Functions taking more than 1% of CPU:

    Function Name Total CPU [unit, %] Self CPU [unit, %] Module
    | - _PyEval_EvalFrameDefault 22180 (99.87%) 4448 (20.03%) python312.dll
    | - _PyCode_CheckLineNumber 5389 (24.27%) 2583 (11.63%) python312.dll
    | - maybe_call_line_trace 6461 (29.09%) 1966 (8.85%) python312.dll
    | - retreat 2242 (10.10%) 1644 (7.40%) python312.dll
    | - [External Call] tracer.cp312-win_amd64.pyd 6274 (28.25%) 1428 (6.43%) tracer.cp312-win_amd64.pyd
    | - get_line_delta 1162 (5.23%) 1162 (5.23%) python312.dll
    | - unicodekeys_lookup_unicode 844 (3.80%) 728 (3.28%) python312.dll
    | - _Py_dict_lookup 1476 (6.65%) 632 (2.85%) python312.dll
    | - call_trace 9802 (44.14%) 630 (2.84%) python312.dll
    | - siphash13 442 (1.99%) 442 (1.99%) python312.dll
    | - PyUnicode_New 745 (3.35%) 376 (1.69%) python312.dll
    | - pymalloc_alloc 362 (1.63%) 362 (1.63%) python312.dll
    | - _PyType_Lookup 1786 (8.04%) 319 (1.44%) python312.dll
    | - set_add_entry 271 (1.22%) 271 (1.22%) python312.dll
    | - PyDict_GetItem 824 (3.71%) 242 (1.09%) python312.dll
    | - call_trace_protected 8562 (38.55%) 236 (1.06%) python312.dll
    | - PyNumber_Subtract 449 (2.02%) 221 (1.00%) python312.dll
  9. sweeneyde commented on Jun 6, 2022

    @sweeneyde
    sweeneyde
    on Jun 6, 2022
    Member
    More actions

    This seems to suggest to me that this could be made much faster via a dedicated C API function:

    https://github.com/nedbat/coveragepy/blob/master/coverage/ctracer/util.h#L41

    #define MyCode_GetCode(co)      (PyObject_GetAttrString((PyObject *)(co), "co_code"))
  10. sweeneyde commented on Jun 6, 2022

    @sweeneyde
    sweeneyde
    on Jun 6, 2022
    Member
    More actions

    Indeed, on main, if I add printf("lookup %s\n", (const char *)PyUnicode_DATA(key)); to the top of unicodekeys_lookup_unicode and run that fib script, I get the following hot loop repeated over and over:

    lookup fib
    lookup C:\Users\sween\Source\Repos\cpython2\cpython\cover.py
    lookup C:\Users\sween\Source\Repos\cpython2\cpython\cover.py
    lookup co_code
    lookup fib
    lookup C:\Users\sween\Source\Repos\cpython2\cpython\cover.py
    lookup C:\Users\sween\Source\Repos\cpython2\cpython\cover.py
    lookup co_code
    ...
    

    Each of these calls to PyObject_GetAttrString(..., "co_code") requires PyUnicode_FromString->unicode_decode_utf8->PyUnicode_New->_PyObject_Malloc, followed by PyObject_GetAttr->_PyObject_GenericGetAttrWithDict->_PyType_Lookup->find_name_in_mro->(unicode_hash+_Py_Dict_Lookup)->unicodekeys_lookup_unicode, where it should be just a C API function call.

  11. sweeneyde commented on Jun 6, 2022

    @sweeneyde
    sweeneyde
    on Jun 6, 2022
    Member
    More actions

    Ah! PyCode_GetCode exists since #92168

  12. markshannon commented on Jun 6, 2022

    @markshannon
    markshannon
    on Jun 6, 2022
    Member
    More actions

    Some performance degradation under tracing is expected, but not as much as reported.
    This is a deliberate tradeoff: faster execution when not tracing, for slower execution when tracing.

    From the profiles that @sweeneyde gathered it looks like we are seeing slowdowns in:

    • _PyEval_EvalFrameDefault, which is expected
    • Calculation of line numbers. This is probably due to the new line table format.

    The new line number table gives better error messages without taking up too much extra space, but it is more complex and thus slower to parse.
    We should be able to speed it up, but don't expect things to be as fast as 3.10. Sorry.

    Once coveragepy/coveragepy#1394 is merged, we can look at the profile again to see if much has changed.

  13. sweeneyde commented on Jun 6, 2022

    @sweeneyde
    sweeneyde
    on Jun 6, 2022
    Member
    More actions
    New profile of fib_cover.py on main branch, with the PyCode_GetCode addition (took 18.1 seconds)
    Function Name Total CPU [unit, %] Self CPU [unit, %] Module
    | - _PyEval_EvalFrameDefault 19239 (99.66%) 4481 (23.21%) python312.dll
    | - _PyCode_CheckLineNumber 5344 (27.68%) 2676 (13.86%) python312.dll
    | - maybe_call_line_trace 6397 (33.14%) 1997 (10.34%) python312.dll
    | - retreat 2197 (11.38%) 1629 (8.44%) python312.dll
    | - [External Call] tracer.cp312-win_amd64.pyd 3073 (15.92%) 1360 (7.04%) tracer.cp312-win_amd64.pyd
    | - get_line_delta 1038 (5.38%) 1038 (5.38%) python312.dll
    | - call_trace 6630 (34.34%) 605 (3.13%) python312.dll
    | - unicodekeys_lookup_unicode 549 (2.84%) 548 (2.84%) python312.dll
    | - _Py_dict_lookup 1060 (5.49%) 511 (2.65%) python312.dll
    | - PyDict_GetItem 929 (4.81%) 266 (1.38%) python312.dll
    | - set_add_entry 265 (1.37%) 265 (1.37%) python312.dll
    | - PyNumber_Subtract 447 (2.32%) 226 (1.17%) python312.dll
    | - call_trace_protected 5408 (28.01%) 222 (1.15%) python312.dll
    | - initialize_locals 199 (1.03%) 199 (1.03%) python312.dll

    I wonder: would there be any way to retain some specializations during tracing? Some specialized opcodes are certainly incorrect to use during tracing, e.g., STORE_FAST__LOAD_FAST. However, couldn't others be retained, e.g. LOAD_GLOBAL_MODULE, or any specialized opcode that covers only one unspecialized opcode? (EDIT: maybe not, if it uses NOTRACE_DISPATCH())

  14. pablogsal commented on Jun 6, 2022

    @pablogsal
    pablogsal
    on Jun 6, 2022
    Member
    More actions

    We should be able to speed it up, but don't expect things to be as fast as 3.10. Sorry.

    Well, two times slower than 3.10, as some of @nedbat's benchmarks show, is not acceptable in my opinion. We should try to get this below 20% at the very least.

    I marked this as a release blocker and this will block any future releases.

  15. 49 remaining items

  16. markshannon
    added a commit that references this issue on Jun 29, 2022

    [3.11] GH-93516: Drop broken assert, fixes GH-93769 (GH-94411)

    9140c41
  17. Fidget-Spinner commented on Jun 30, 2022

    @Fidget-Spinner
    Fidget-Spinner
    on Jun 30, 2022
    Member
    More actions

    I now agree with Gregory and Alex that the performance regression is within acceptable levels. IMO we can leave this issue open but it shouldn't block the 3.11 release.

  18. jpe commented on Jul 1, 2022

    @jpe
    jpe
    on Jul 1, 2022
    More actions

    The current sources seem to be significantly faster than b3.

    Can the line number checks be eliminated when frame->f_trace_lines is false? This wouldn't help with coverage but would help debuggers that set f_trace_lines to false when line events aren't needed.

  19. fabioz commented on Jul 1, 2022

    @fabioz
    fabioz
    on Jul 1, 2022
    Contributor
    More actions

    I just reran the performance for pydevd with the current tip. The numbers haven't changed from the last run (it's still in the range of 45 -> 70 percent slower depending on the use case, so, at least for pydevd the current tip measurements are very close to the beta 3 measurements).

    Benchmark Python 3.10 Python 3.11 tip ( abf5f5c ) Slower than 3.10 in %
    method_calls_with_breakpoint 0,25 0,363 45,20%
    method_calls_without_breakpoint 0,247 0,375 51,82%
    method_calls_with_step_over 0,236 0,376 59,32%
    method_calls_with_exception_breakpoint 0,249 0,389 56,22%
    global_scope_1_with_breakpoint 0,557 0,937 68,22%
    global_scope_2_with_breakpoint 0,238 0,343 44,12%
  20. jpe commented on Jul 1, 2022

    @jpe
    jpe
    on Jul 1, 2022
    More actions

    Hmm, I may be confusing myself but I'm seeing tip take about 50% of the time of b3 when running one of our test files. I'm testing with a C trace function that doesn't do anything except return 0. Looking at the diffs since b3, I think performance partially depends on the number of code objects.

  21. gvanrossum
    assigned
    markshannon
    on Jul 3, 2022
  22. P403n1x87 commented on Jul 6, 2022

    @P403n1x87
    P403n1x87
    on Jul 6, 2022
    Contributor
    More actions

    On the off chance that some native flame graphs could be of help here. These are taken from running @nedbat's benchmarks for the bm_sudoku case, through austinp, comparing the tip of 3.11 (b22f9d6) against 3.10.5. The side view also shows a top-like report, sorted by own time.

    bm_sudoku with 3.10
    bm_sudoku with 3.11
  23. pablogsal commented on Jul 6, 2022

    @pablogsal
    pablogsal
    on Jul 6, 2022
    Member
    More actions

    Thanks for the report @P403n1x87! Unfortunately, the results you show are not very useful (is mostly what we are already getting by running perf) because what we need to understand is what linear or non-linear combination of the changes we did are affecting the coverage numbers. The flame graphs and tables show the difference between 3.10.5 and 3.11 which are two completely code paths for everything (they don't even have the same instructions). What we need to understand is the relative cost of the coverage-relative machinery before and after every change that we did that could affect this. Unfortunately tracing over all the interpreter is too unstable and noisy (and not granular enough) to give us insight.

    But thanks a lot for the help 🙏, I am sure that if we need to measure the interaction between the benchmark python code and the interpreter we can use your tool 👍

  24. pablogsal
    added
    deferred-blocker
    and removed
    release-blocker
    on Jul 8, 2022
  25. pablogsal
    removed
    deferred-blocker
    on Aug 5, 2022
  26. iritkatriel
    added a commit that references this issue on Aug 16, 2023

    pythonGH-93516: match logic of codeobject.c in _co_firsttraceable cal…

    a1aaf9a
  27. iritkatriel
    added
    interpreter-core(Objects, Python, Grammar, and Parser dirs)(Objects, Python, Grammar, and Parser dirs)
    on Nov 24, 2023
  28. StanFromIreland commented on Apr 25, 2025

    @StanFromIreland
    StanFromIreland
    on Apr 25, 2025
    Member
    More actions

    This needs to be removed from the differed/release blocker project. (Along with a few others)

  29. markshannon commented on May 6, 2025

    @markshannon
    markshannon
    on May 6, 2025
    Member
    More actions

    There are going to be no direct fixes for this issue, but I expect it to become a non-issue over the course of the 3.15 development cycle for two reasons:

    • Now that sys.monitoring supports efficient branch monitoring, coverage.py will use it by default and the performance impact on coverage will go. And really, it is only coverage that this matters for. All debuggers now use sys.monitoring.
    • As we move to tagged integers the boxing/unboxing overhead for line events will disappear, which is the main cause of the slowdown.

    @nedbat is my statement about coverage.py correct?

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

Metadata

Metadata

Assignees

  • @markshannon
    markshannon

Labels

3.11only security fixesonly security fixesinterpreter-core(Objects, Python, Grammar, and Parser dirs)(Objects, Python, Grammar, and Parser dirs)type-bugAn unexpected behavior, bug, or errorAn unexpected behavior, bug, or error

Projects

  • Release and Deferred blockers 🚫
    Status
    No status
    Show more project fields

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.