python / python/cpython

`METH_METHOD` calling convention is now not so efficient

Aperta
#123,500 1 commento 0 reazioni 0 assegnatari Vedi su GitHub

Nessuno ha ancora preso questa issue.

performance topic-C-API type-bug
Lingua principale
Python
Stelle
77.2k
Fork
35.9k
Metriche di merge delle PR
Metriche PR in attesa

Descrizione

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

Guida per i contributori

Apri la guida per i contributori

Come iniziare

  1. Leggi tutta la issue e poi la guida ai contributi del progetto.
  2. Commenta sulla issue per dire che te ne occupi tu — evita che due persone facciano lo stesso lavoro.
  3. Fai un fork del repository e lavora su un branch.
  4. Apri una pull request che faccia riferimento al numero della issue.

Direzione di ricerca

Inizia riproducendo i benchmark riportati per CDataType_from_buffer_copy() in _ctypes.c e dec_mpd_qquantize() in _decimal.c, confrontando METH_METHOD|METH_FASTCALL con METH_FASTCALL e con le chiamate di ricerca del modulo mostrate. Leggi i punti di ingresso METH_METHOD e PyType_GetModuleByDef, quindi definisci e convalida un approccio accettato le cui prestazioni non siano peggiori di quelle delle alternative nei casi citati.

Scritto dal modello di indicizzazione a partire dal testo della issue.

Valutazione

Stack tecnologico
c, python
Ambito
backend, performance
Tipo di issue
Bug
Difficoltà
5/5
Tempo stimato
Più di una settimana
Stato di attività
Ferma
Chiarezza
Da chiarire
Idoneità per principianti
25/100

Ricevi le nuove issue nella tua casella

Un breve riepilogo di issue GitHub adatte ai principianti.