Lavorare attraverso il principio della responsabilità singola (SRP) in Python quando le chiamate sono costose


12

Alcuni punti base:

  • Le chiamate al metodo Python sono "costose" a causa della sua natura interpretata . In teoria, se il tuo codice è abbastanza semplice, abbattere il codice Python ha un impatto negativo oltre alla leggibilità e al riutilizzo ( che è un grande vantaggio per gli sviluppatori, non tanto per gli utenti ).
  • Il principio della responsabilità singola (SRP) mantiene il codice leggibile, è più facile da testare e mantenere.
  • Il progetto ha un tipo speciale di background in cui vogliamo codice leggibile, test e prestazioni temporali.

Ad esempio, un codice come questo che invoca diversi metodi (x4) è più lento di quello successivo che è solo uno.

from operator import add

class Vector:
    def __init__(self,list_of_3):
        self.coordinates = list_of_3

    def move(self,movement):
        self.coordinates = list( map(add, self.coordinates, movement))
        return self.coordinates

    def revert(self):
        self.coordinates = self.coordinates[::-1]
        return self.coordinates

    def get_coordinates(self):
        return self.coordinates

## Operation with one vector
vec3 = Vector([1,2,3])
vec3.move([1,1,1])
vec3.revert()
vec3.get_coordinates()

In confronto a questo:

from operator import add

def move_and_revert_and_return(vector,movement):
    return list( map(add, vector, movement) )[::-1]

move_and_revert_and_return([1,2,3],[1,1,1])

Se devo parallelizzare qualcosa del genere, è piuttosto obiettivo perdere prestazioni. Ricorda che è solo un esempio; il mio progetto ha diverse mini routine con questo tipo di matematica - Anche se è molto più facile lavorare con i nostri profiler, non mi piace.


Come e dove possiamo abbracciare l'SRP senza compromettere le prestazioni in Python, poiché la sua implementazione intrinseca lo influenza direttamente?

Ci sono soluzioni alternative, come una sorta di pre-processore che mette le cose in linea per il rilascio?

O Python è semplicemente povero nel gestire la suddivisione del codice del tutto?



19
Per quello che vale, i tuoi due esempi di codice non differiscono nel numero di responsabilità. L'SRP non è un esercizio di conteggio dei metodi.
Robert Harvey,

2
@RobertHarvey Hai ragione, scusa per il cattivo esempio e ne modificherò uno migliore quando avrò tempo. In entrambi i casi, la leggibilità e la manutenibilità soffrono e alla fine l'SRP si rompe all'interno della base di codice mentre riduciamo le classi e i loro metodi.
lucasgcb,

4
si noti che le chiamate di funzione sono costose in qualsiasi lingua , anche se i compilatori AOT hanno il lusso di inline
Eevee

6
Utilizzare un'implementazione JITted di Python come PyPy. Dovrebbe principalmente risolvere questo problema.
Bakuriu,

Risposte:


17

Python è semplicemente povero nel gestire la scomposizione del codice?

Sfortunatamente sì, Python è lento e ci sono molti aneddoti sulle persone che aumentano drasticamente le prestazioni incorporando funzioni e rendendo brutto il loro codice.

C'è un lavoro in giro, Cython, che è una versione compilata di Python e molto più veloce.

- Modifica Volevo solo affrontare alcuni dei commenti e altre risposte. Anche se la loro spinta non è forse specifica per Python. ma ottimizzazione più generale.

  1. Non ottimizzare fino a quando non hai un problema, quindi cerca i colli di bottiglia

    Generalmente un buon consiglio. Ma l'ipotesi è che il codice "normale" sia di solito performante. Questo non è sempre il caso. Le singole lingue e strutture hanno ciascuna le proprie idiosincrasie. In questo caso la funzione chiama.

  2. Sono solo pochi millisecondi, altre cose saranno più lente

    Se stai eseguendo il tuo codice su un potente computer desktop, probabilmente non ti interessa finché il tuo codice utente singolo viene eseguito in pochi secondi.

    Ma il codice aziendale tende a essere eseguito per più utenti e richiede più di una macchina per supportare il carico. Se il tuo codice viene eseguito due volte più velocemente significa che puoi avere il doppio del numero di utenti o la metà del numero di macchine.

    Se possiedi i tuoi computer e data center, in genere hai una grande quantità di sovraccarico in termini di potenza della CPU. Se il tuo codice funziona un po 'lentamente, puoi assorbirlo, almeno fino a quando non devi acquistare un secondo computer.

    In questi giorni di cloud computing in cui si utilizza solo esattamente la potenza di calcolo richiesta e non più, vi è un costo diretto per il codice non performante.

    Il miglioramento delle prestazioni può ridurre drasticamente la spesa principale per un'azienda basata su cloud e le prestazioni dovrebbero essere realmente al centro.


1
Mentre la risposta di Robert aiuta a coprire alcune basi per potenziali equivoci alla base di questo tipo di ottimizzazione (che si adatta a questa domanda ), ritengo che questo risponda alla situazione un po 'più direttamente e in linea con il contesto di Python.
lucasgcb,

2
scusa è un po 'corto. Non ho tempo di scrivere di più. Ma penso che Robert abbia torto su questo. Il miglior consiglio con Python sembra essere quello di profilare mentre si codifica. Non dare per scontato che sarà performante e ottimizza solo se trovi un problema
Ewan,

2
@Ewan: non devi prima scrivere l'intero programma per seguire il mio consiglio. Un metodo o due è più che sufficiente per ottenere una profilazione adeguata.
Robert Harvey,

1
puoi anche provare pypy, che è un pitone JITted
Eevee

2
@Ewan Se sei davvero preoccupato per l'overhead delle prestazioni delle chiamate di funzione, qualsiasi cosa tu stia facendo probabilmente non è adatta per Python. Ma poi non riesco davvero a pensare a molti esempi lì. La stragrande maggioranza del codice aziendale è limitata all'IO e le cose pesanti della CPU vengono generalmente gestite chiamando le librerie native (numpy, tensorflow e così via).
Voo,

50

Molti potenziali problemi di prestazioni non sono realmente un problema nella pratica. Il problema che sollevi potrebbe essere uno di questi. In volgare, chiamiamo preoccuparsi di quei problemi senza la prova che si tratta di problemi reali ottimizzazione prematura.

Se si sta scrivendo un front-end per un servizio Web, le chiamate di funzione non influiranno in modo significativo sulle prestazioni, poiché il costo dell'invio di dati su una rete supera di gran lunga il tempo necessario per effettuare una chiamata di metodo.

Se stai scrivendo un ciclo stretto che aggiorna uno schermo video sessanta volte al secondo, allora potrebbe importare. Ma a quel punto, sostengo che hai problemi più grandi se stai cercando di usare Python per farlo, un lavoro per il quale Python probabilmente non è adatto.

Come sempre, il modo in cui lo scopri è misurare. Esegui un profiler delle prestazioni o alcuni timer sul codice. Vedi se è un vero problema in pratica.


Il principio di responsabilità unica non è una legge o un mandato; è una linea guida o un principio. La progettazione del software riguarda sempre i compromessi; non ci sono assoluti. Non è raro compromettere la leggibilità e / o la manutenibilità per la velocità, quindi potrebbe essere necessario sacrificare SRP sull'altare delle prestazioni. Ma non fare questo compromesso a meno che tu non sappia di avere un problema di prestazioni.


3
Penso che questo fosse vero, fino a quando non abbiamo inventato il cloud computing. Ora una delle due funzioni costa effettivamente 4 volte di più dell'altra
Ewan,

2
@Ewan 4 volte potrebbe non importare fino a quando non lo avrai misurato per essere abbastanza significativo da preoccuparti. Se Foo impiega 1 ms e Bar impiega 4 ms, non va bene. Fino a quando non ti rendi conto che la trasmissione dei dati attraverso la rete richiede 200 ms. A quel punto, essendo Bar più lento non importa molto. (Solo un possibile esempio di dove essere X volte più lento non fa una differenza evidente o di impatto, non deve essere necessariamente super realistico.)
Becuzz

8
@Ewan Se la riduzione della bolletta ti fa risparmiare $ 15 al mese ma ci vorranno un appaltatore da $ 125 all'ora per 4 ore per risolverlo e testarlo, potrei facilmente giustificare che non vale il tempo di un'azienda (o almeno non fare bene ora se il time to market è cruciale, ecc.). Ci sono sempre dei compromessi. E ciò che ha senso in una circostanza potrebbe non in un'altra.
Becuzz,

3
le tue bollette AWS sono davvero molto basse
Ewan,

6
@Ewan AWS arrotonda comunque al soffitto per lotti (lo standard è 100ms). Ciò significa che questo tipo di ottimizzazione ti consente di risparmiare qualsiasi cosa se evita costantemente di passare al blocco successivo.
Delioth,

2

Innanzitutto, alcuni chiarimenti: Python è una lingua. Esistono diversi interpreti che possono eseguire il codice scritto nel linguaggio Python. L'implementazione di riferimento (CPython) è di solito ciò a cui viene fatto riferimento quando qualcuno parla di "Python" come se fosse un'implementazione, ma è importante essere precisi quando si parla di caratteristiche delle prestazioni, poiché possono differire selvaggiamente tra implementazioni.

Come e dove possiamo abbracciare l'SRP senza compromettere le prestazioni in Python, poiché la sua implementazione intrinseca lo influenza direttamente?

Caso 1.) Se hai un codice Python puro (<= Python Language versione 3.5, 3.6 ha "supporto di livello beta") che si basa solo su moduli Python puri, puoi abbracciare SRP ovunque e usare PyPy per eseguirlo. PyPy ( https://morepypy.blogspot.com/2019/03/pypy-v71-released-now-uses-utf-8.html ) è un interprete Python che ha un compilatore Just in Time (JIT) e può rimuovere la funzione chiamare overhead fintanto che ha il tempo sufficiente per "riscaldarsi" tracciando il codice eseguito (pochi secondi IIRC). **

Se si è limitati a utilizzare l'interprete CPython, è possibile estrarre le funzioni lente in estensioni scritte in C, che saranno precompilate e non subiranno alcun sovraccarico di interprete. Puoi comunque utilizzare SRP ovunque, ma il tuo codice verrà suddiviso tra Python e C. Se questo è meglio o peggio per manutenibilità rispetto all'abbandono selettivo di SRP ma attenersi al solo codice Python dipende dal tuo team, ma se hai sezioni critiche in termini di prestazioni del tuo codice, sarà indubbiamente più veloce del codice Python puro più ottimizzato interpretato da CPython. Molte delle più veloci librerie matematiche di Python usano questo metodo (numpy e scipy IIRC). Che è un bel seguito nel caso 2 ...

Caso 2.) Se si dispone di codice Python che utilizza estensioni C (o si basa su librerie che utilizzano estensioni C), PyPy potrebbe essere utile o meno a seconda della modalità di scrittura. Vedi http://doc.pypy.org/it/latest/extending.html per i dettagli, ma il sommario è che CFFI ha un sovraccarico minimo mentre CTypes è più lento (usarlo con PyPy potrebbe essere anche più lento di CPython)

Cython ( https://cython.org/ ) è un'altra opzione con cui non ho molta esperienza. Lo cito per completezza, quindi la mia risposta può "essere autonoma", ma non rivendicare alcuna competenza. Dal mio uso limitato, mi è sembrato di dover lavorare di più per ottenere gli stessi miglioramenti di velocità che avrei potuto ottenere "gratuitamente" con PyPy, e se avessi bisogno di qualcosa di meglio di PyPy, sarebbe stato altrettanto facile scrivere la mia estensione C ( che ha il vantaggio se riutilizzo il codice altrove o ne estraggo parte in una libreria, tutto il mio codice può ancora essere eseguito con qualsiasi interprete Python e non è richiesto che sia eseguito da Cython).

Ho paura di essere "bloccato in" Cython, mentre qualsiasi codice scritto per PyPy può funzionare anche con CPython.

** Alcune note extra su PyPy in Production

Fai molta attenzione a fare qualsiasi scelta che abbia l'effetto pratico di "bloccarti" su PyPy in una base di codice di grandi dimensioni. Poiché alcune librerie di terze parti (molto popolari e utili) non funzionano bene per i motivi menzionati in precedenza, può causare decisioni molto difficili in seguito se ti rendi conto che hai bisogno di una di quelle librerie. La mia esperienza è principalmente nell'uso di PyPy per accelerare alcuni (ma non tutti) microservizi sensibili alle prestazioni in un ambiente aziendale in cui si aggiunge una complessità trascurabile al nostro ambiente di produzione (abbiamo già implementato più lingue, alcune con diverse versioni principali come 2.7 vs 3.5 esecuzione comunque).

Ho scoperto che l'uso di PyPy e CPython mi costringeva regolarmente a scrivere codice che si basa solo sulle garanzie fornite dalle specifiche del linguaggio stesso e non sui dettagli di implementazione che sono soggetti a modifiche in qualsiasi momento. Potresti trovare che pensare a tali dettagli sia un onere in più, ma l'ho trovato prezioso nel mio sviluppo professionale e penso che sia "salutare" per l'ecosistema Python nel suo insieme.


Si! Ho pensato di concentrarmi sulle estensioni C per questo caso invece di abbandonare il principio e scrivere il codice jolly, le altre risposte mi davano l'impressione che sarebbe lento a prescindere se non avessi scambiato dall'interprete di riferimento - Per chiarire, OOP sarebbe ancora essere un approccio ragionevole secondo te?
lucasgcb,

1
con il caso 1 (secondo comma) non si ottiene lo stesso overhead chiamando le funzioni, anche se le funzioni stesse sono rispettate?
Ewan,

CPython è l'unico interprete che viene generalmente preso sul serio. PyPy è interessante , ma certamente non vede alcun tipo di adozione diffusa. Inoltre, il suo comportamento differisce da CPython e non funziona con alcuni pacchetti importanti, ad esempio scipy. Pochi sviluppatori intelligenti raccomanderebbero PyPy per la produzione. Come tale, la distinzione tra la lingua e l'implementazione è in pratica irrilevante.
jpmc26

Penso che tu abbia colpito l'unghia sulla testa però. Non c'è motivo per cui non potresti avere un interprete migliore o un compilatore. Non è intrinseco a Python come linguaggio. Sei solo bloccato con realtà pratiche
Ewan

@ jpmc26 Ho usato PyPy in produzione e mi raccomando di farlo ad altri sviluppatori esperti. È ottimo per i microservizi che usano falconframework.org per le API di riposo leggero (come un esempio). Il comportamento differisce perché gli sviluppatori si affidano a dettagli di implementazione che NON sono una garanzia della lingua non è un motivo per non usare PyPy. È un motivo per riscrivere il tuo codice. Lo stesso codice può comunque rompersi se CPython apporta modifiche alla sua implementazione (cosa che è libero di fare purché sia ​​ancora conforme alle specifiche del linguaggio).
Steven Jackson,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.