python / python/cpython

`METH_METHOD` calling convention is now not so efficient

Offen
#123,500 1 Kommentar 0 Reaktionen 0 zugewiesene Personen Auf GitHub ansehen

Dieses Issue hat noch niemand übernommen.

performance topic-C-API type-bug
Vorherrschende Sprache
Python
Sterne
77.2k
Forks
35.9k
PR-Merge-Kennzahlen
PR-Kennzahlen ausstehend

Beschreibung

Bug report

Bug description:

There are callables implemented with the METH_METHOD|METH_FASTCALL signature in C. They can be 5%-15% less efficient than using only METH_FASTCALL (or METH_O) with a PyType_GetModuleByDef function call.

For example, I measured the difference on Windows PGO builds by duplicating functions:

  • CDataType_from_buffer_copy() in _ctypes.c, which is not called when profiling:

    from timeit import timeit
    setup = """if 1:
        import ctypes
        buf = bytearray(16)
        cls = ctypes.c_char * len(buf)
    """
    # with a warmup
    for _ in range(2):
        # METH_METHOD|METH_FASTCALL (as-is)
        r0 = timeit(s0 := f'cls.from_buffer_copy (buf)', setup)
    
        # METH_FASTCALL (no `defining_class`) + PyType_GetModuleByDef
        r1 = timeit(s1 := f'cls.from_buffer_copy1(buf)', setup)
    
    print(s0, r0, 1 + (1 - r0 / r0))
    print(s1, r1, 1 + (1 - r1 / r0))
    
    cls.from_buffer_copy (buf) 0.15552800190635024 1.0
    cls.from_buffer_copy1(buf) 0.13187471489945893 1.1520837837364741
    
  • dec_mpd_qquantize() in _decimal.c profiled with 6800 calls (unfair?):

    # legacy (as-is)
    d1.quantize (d2) 0.1694609627971658 1.0
    
    # METH_METHOD|METH_FASTCALL (`defining_class`) + _PyType_GetModuleState
    d1.quantize1(d2) 0.1408861404022900 1.168621857938327
    
    # METH_FASTCALL (no `defining_class`) + PyType_GetModuleByDef
    d1.quantize2(d2) 0.1258157708973158 1.257553074049807
    
    Script (expand)
    from timeit import timeit
    setup = """if 1:
        from _decimal import Decimal
        d1,d2 = Decimal(1.414), Decimal('0.01')
    """
    for _ in range(2):
        r0 = timeit(s0 := f'd1.quantize (d2)', setup)
        r1 = timeit(s1 := f'd1.quantize1(d2)', setup)
        r2 = timeit(s2 := f'd1.quantize2(d2)', setup)
    
    print(s0, r0, 1 + (1 - r0 / r0))
    print(s1, r1, 1 + (1 - r1 / r0))
    print(s2, r2, 1 + (1 - r2 / r0))
    

Observations:

  • The number of arguments had little to do with this.
  • The gaps seem to be consistent as long as they are equally (un)exercised.
  • The same goes for non-PGO builds and builtin modules (e.g. _sre), where the impacts may be less significant.
CPython versions tested on:

CPython main branch

Operating systems tested on:

Windows

Beitragsleitfaden

Beitragsleitfaden öffnen

Erste Schritte

  1. Lies das ganze Issue und danach den Beitragsleitfaden des Projekts.
  2. Schreib ins Issue, dass du es übernimmst — das erspart doppelte Arbeit.
  3. Forke das Repository und arbeite in einem Branch.
  4. Öffne einen Pull Request, der die Issue-Nummer nennt.

Rechercherichtung

Beginne damit, die gemeldeten Benchmarks für CDataType_from_buffer_copy() in _ctypes.c und dec_mpd_qquantize() in _decimal.c zu reproduzieren, und vergleiche dabei METH_METHOD|METH_FASTCALL mit METH_FASTCALL sowie den gezeigten Aufrufen zur Modulsuche. Lies die Einstiegspunkte METH_METHOD und PyType_GetModuleByDef, definiere anschließend einen akzeptierten Ansatz und validiere ihn, dessen Leistung in den angeführten Fällen nicht schlechter als die der Alternativen ist.

Vom Indexierungsmodell aus dem Issue-Text verfasst.

Bewertung

Tech-Stack
c, python
Bereich
backend, performance
Issue-Typ
Bug
Schwierigkeit
5/5
Geschätzter Aufwand
Über eine Woche
Aktivitätsstatus
Veraltet
Klarheit
Muss geklärt werden
Anfängerfreundlichkeit
25/100

Neue Issues direkt in Ihr Postfach

Eine kurze Übersicht über anfängerfreundliche GitHub-Issues.