Cum să vă optimizați scripturile Python pentru o performanță mai bună

Cum Sa Va Optimizati Scripturile Python Pentru O Performanta Mai Buna



Optimizarea scripturilor Python pentru o performanță mai bună implică identificarea și abordarea blocajelor din codul nostru, făcându-l să ruleze mai rapid și mai eficient. Python este un limbaj de programare popular și puternic, care este folosit în numeroase aplicații în zilele noastre, inclusiv analiza datelor, proiecte ML (învățare automată), dezvoltare web și multe altele. Optimizarea codului Python este o strategie de îmbunătățire a vitezei și eficienței programului de dezvoltare atunci când desfășoară orice activitate folosind mai puține linii de cod, mai puțină memorie sau resurse suplimentare. Codul mare și ineficient poate încetini programul, ceea ce poate duce la o satisfacție slabă a clienților și la o potențială pierdere financiară sau la necesitatea de mai multă muncă pentru remediere și depanare.

Este necesar în timp ce efectuați o sarcină care necesită procesarea mai multor acțiuni sau date. Prin urmare, schimbarea și îmbunătățirea unor blocuri de cod și funcționalități ineficiente poate avea rezultate uimitoare, cum ar fi următoarele:

  1. Creșteți performanța aplicației
  2. Creați cod lizibil și organizat
  3. Faceți monitorizarea și depanarea erorilor mai simple
  4. Conservați o putere de calcul considerabilă și așa mai departe

Profilați codul dvs

Înainte de a începe optimizarea, este esențial să identificăm părțile codului de proiect care îl încetinesc. Tehnicile de profilare în Python includ pachetele cProfile și profile. Utilizați astfel de instrumente pentru a evalua cât de repede se execută anumite funcții și linii de cod. Modulul cProfile produce un raport care detaliază cât timp durează fiecare funcție de script pentru a rula. Acest raport ne poate ajuta să găsim orice funcții care rulează lent, astfel încât să le putem îmbunătăți.







Fragment de cod:



import cProfil la fel de cP
def calculateSum ( inputNumber ) :
suma_numerelor_de_intrare = 0
in timp ce inputNumber > 0 :
suma_numerelor_intrarilor + = inputNumber % 10
inputNumber // = 10
imprimare ( „Suma tuturor cifrelor din numărul de intrare este: „sum_of_input_numbers”” )
întoarcere suma_numerelor_de_intrare
def principal_func ( ) :
cP. alerga ( 'calculateSum(9876543789)' )
dacă __Nume__ == '__principal__' :
principal_func ( )

Programul efectuează un total de cinci apeluri de funcții, așa cum se vede în prima linie a rezultatului. Detaliile fiecărui apel de funcție sunt afișate în următoarele câteva rânduri, inclusiv de câte ori a fost invocată funcția, durata totală de timp în funcție, durata de timp per apel și cantitatea totală de timp în funcție (inclusiv toate funcțiile pe care le numesc).



În plus, programul tipărește un raport pe ecranul prompt care arată că programul finalizează timpul de execuție a tuturor sarcinilor sale în 0.000 de secunde. Aceasta arată cât de rapid este programul.





Alegeți structura de date potrivită

Caracteristicile de performanță depind de structura datelor. În special, dicționarele sunt mai rapide pentru căutări decât listele privind stocarea de uz general. Selectați structura de date care este cea mai potrivită pentru operațiunile pe care le vom efectua asupra datelor dumneavoastră dacă le cunoașteți. Următorul exemplu investighează eficiența diferitelor structuri de date pentru un proces identic pentru a determina dacă un element din structura de date este prezent.



Evaluăm timpul necesar pentru a verifica dacă un element este prezent în fiecare structură de date - o listă, un set și un dicționar - și le comparăm.

OptimizeDataType.py:

import Timei la fel de tt
import Aleatoriu la fel de rndobj
# Generați o listă de numere întregi
listă_date_aleatoare = [ rndobj. randint ( 1 , 10000 ) pentru _ în gamă ( 10000 ) ]
# Creați un set din aceleași date
set_date_aleatoare = a stabilit ( listă_date_aleatoare )

# Creați un dicționar cu aceleași date ca și cheile
obj_DataDictionary = { pe unu: Nici unul pentru pe unu în listă_date_aleatoare }

# Element de căutat (există în date)
număr_aleatoriu_de_găsit = rndobj. alegere ( listă_date_aleatoare )

# Măsurați timpul pentru a verifica calitatea de membru într-o listă
list_time = tt. Timei ( lambda : număr_aleatoriu_de_găsit în listă_date_aleatoare , număr = 1000 )

# Măsurați timpul pentru a verifica calitatea de membru într-un set
potriveste ora = tt. Timei ( lambda : număr_aleatoriu_de_găsit în set_date_aleatoare , număr = 1000 )

# Măsurați timpul pentru a verifica calitatea de membru într-un dicționar
dict_time = tt. Timei ( lambda : număr_aleatoriu_de_găsit în obj_DataDictionary , număr = 1000 )

imprimare ( f „Timp de verificare a calității de membru: {list_time:.6f} secunde” )
imprimare ( f „Setați ora de verificare a calității de membru: {set_time:.6f} secunde” )
imprimare ( f „Timp de verificare a apartenenței la dicționar: {dict_time:.6f} secunde” )

Acest cod compară performanța listelor, seturilor și dicționarelor atunci când efectuează verificări de membru. În general, seturile și dicționarele sunt substanțial mai rapide decât listele pentru testele de membru, deoarece folosesc căutări bazate pe hash, deci au o complexitate de timp medie de O(1). Listele, pe de altă parte, trebuie să facă căutări liniare care au ca rezultat teste de apartenență cu complexitate de timp O(n).

  O captură de ecran a unui computer Descriere generată automat

Utilizați funcțiile încorporate în loc de bucle

Numeroase funcții sau metode încorporate în Python pot fi utilizate pentru a îndeplini sarcini tipice precum filtrarea, sortarea și maparea. Folosirea acestor rutine, mai degrabă decât crearea buclelor, ajută la accelerarea codului, deoarece acestea sunt frecvent optimizate pentru performanță.

Să construim un exemplu de cod pentru a compara performanța creării de bucle personalizate utilizând funcțiile încorporate pentru joburi tipice (cum ar fi map(), filter() și sorted()). Vom evalua cât de bine funcționează diferitele metode de cartografiere, filtrare și sortare.

BuiltInFunctions.py:

import Timei la fel de tt
# Exemplu de listă de numere_listă
listă_numere = listă ( gamă ( 1 , 10000 ) )

# Funcție pentru pătrat numere_list folosind o buclă
def square_using_loop ( listă_numere ) :
pătrat_rezultat = [ ]
pentru pe unu în lista_numerelor:
pătrat_rezultat. adăuga ( pe unu ** 2 )
întoarcere pătrat_rezultat
# Funcție de filtrare a listei de numere pare folosind o buclă
def filter_even_using_loop ( listă_numere ) :
filter_result = [ ]
pentru pe unu în lista_numerelor:
dacă pe unu % 2 == 0 :
filter_result. adăuga ( pe unu )
întoarcere filter_result
# Funcție de sortare numere_list folosind o buclă
def sort_using_loop ( listă_numere ) :
întoarcere sortat ( listă_numere )
# Măsurați timpul până la pătrat numere_list folosind map()
map_time = tt. Timei ( lambda : listă ( Hartă ( lambda x: x ** 2 , listă_numere ) ) , număr = 1000 )
# Măsurați timpul de filtrare a numerelor pare folosind filter()
timp_filtru = tt. Timei ( lambda : listă ( filtru ( lambda x: x % 2 == 0 , listă_numere ) ) , număr = 1000 )
# Măsurați timpul pentru sortarea numerelor_list folosind sorted()
timp_sortat = tt. Timei ( lambda : sortat ( listă_numere ) , număr = 1000 )
# Măsurați timpul până la pătrarea numerelor_list folosind o buclă
loop_map_time = tt. Timei ( lambda : square_using_loop ( listă_numere ) , număr = 1000 )
# Măsurați timpul de filtrare a listei de numere pare folosind o buclă
timp_filtru_buclă = tt. Timei ( lambda : filter_even_using_loop ( listă_numere ) , număr = 1000 )
# Măsurați timpul pentru sortarea numerelor_list folosind o buclă
loop_sorted_time = tt. Timei ( lambda : sort_using_loop ( listă_numere ) , număr = 1000 )
imprimare ( „Lista de numere conține 10000 de elemente” )
imprimare ( f „Timp Map(): {map_time:.6f} secunde” )
imprimare ( f „Timp Filter(): {filter_time:.6f} secunde” )
imprimare ( f „Timp sortat(): {sorted_time:.6f} secunde” )
imprimare ( f „Timp buclă (hartă): {loop_map_time:.6f} secunde” )
imprimare ( f „Timp buclă (filtru): {loop_filter_time:.6f} secunde” )
imprimare ( f „Timp buclă (sortată): {loop_sorted_time:.6f} secunde” )

Probabil vom observa că funcțiile încorporate (map(), filter() și sorted()) sunt mai rapide decât buclele personalizate pentru aceste sarcini comune. Funcțiile încorporate în Python oferă o abordare mai concisă și mai ușor de înțeles pentru a îndeplini aceste sarcini și sunt foarte optimizate pentru performanță.

Optimizați buclele

Dacă este necesară scrierea buclelor, există câteva tehnici pe care le putem face pentru a le accelera. În general, bucla range() este mai rapidă decât repetarea înapoi. Acest lucru se datorează faptului că range() generează un iterator fără a inversa lista, ceea ce poate fi o operațiune costisitoare pentru liste lungi. În plus, deoarece range() nu construiește o listă nouă în memorie, folosește mai puțină memorie.

OptimizeLoop.py:

import Timei la fel de tt
# Exemplu de listă de numere_listă
listă_numere = listă ( gamă ( 1 , 100000 ) )
# Funcție pentru a repeta lista în ordine inversă
def loop_reverse_iteration ( ) :
rezultat_revers = [ ]
pentru j în gamă ( numai ( listă_numere ) - 1 , - 1 , - 1 ) :
rezultat_revers. adăuga ( listă_numere [ j ] )
întoarcere rezultat_revers
# Funcție de iterare peste listă folosind range()
def loop_range_iteration ( ) :
interval_rezultat = [ ]
pentru k în gamă ( numai ( listă_numere ) ) :
interval_rezultat. adăuga ( listă_numere [ k ] )
întoarcere interval_rezultat
# Măsurați timpul necesar pentru a efectua iterația inversă
timp_invers = tt. Timei ( loop_reverse_iteration , număr = 1000 )
# Măsurați timpul necesar pentru a efectua iterația intervalului
interval_timp = tt. Timei ( loop_range_iteration , număr = 1000 )
imprimare ( „Lista de numere conține 100000 de înregistrări” )
imprimare ( f „Timp de iterație inversă: {reverse_time:.6f} secunde” )
imprimare ( f „Timp de iterare a intervalului: {range_time:.6f} secunde” )

Evitați apelurile de funcții inutile

Există o suprasarcină de fiecare dată când este apelată o funcție. Codul rulează mai repede dacă se evită apelurile de funcții inutile. De exemplu, în loc să executați în mod repetat o funcție care calculează o valoare, încercați să stocați rezultatul calculului într-o variabilă și să o utilizați.

Instrumente pentru profilare

Pentru a afla mai multe despre performanța codului dvs., pe lângă profilarea încorporată, putem utiliza pachetele externe de profilare precum cProfile, Pyflame sau SnakeViz.

Cache rezultate

Dacă codul nostru trebuie să efectueze calcule costisitoare, am putea lua în considerare stocarea în cache a rezultatelor pentru a economisi timp.

Refactorizarea codului

Refactorizarea codului pentru a facilita citirea și întreținerea este uneori o parte necesară a optimizării acestuia. Un program mai rapid poate fi, de asemenea, mai curat.

Utilizați compilarea Just-in-Time (JIT)

Bibliotecile precum PyPy sau Numba pot oferi o compilație JIT care poate accelera semnificativ anumite tipuri de cod Python.

Actualizați Python

Asigurați-vă că utilizați cea mai recentă versiune de Python, deoarece versiunile mai noi includ adesea îmbunătățiri de performanță.

Paralelism și concurență

Pentru procesele care pot fi paralelizate, investigați tehnicile paralele și de sincronizare precum multiprocesare, threading sau asincronizare.

Amintiți-vă că benchmarking-ul și profilarea ar trebui să fie principalii factori de optimizare. Concentrați-vă pe îmbunătățirea zonelor codului nostru care au cele mai semnificative efecte asupra performanței și testați-vă în mod constant îmbunătățirile pentru a vă asigura că au efectele dorite fără a introduce mai multe defecte.

Concluzie

În concluzie, optimizarea codului Python este crucială pentru îmbunătățirea performanței și a eficienței resurselor. Dezvoltatorii pot crește foarte mult viteza de execuție și receptivitatea aplicațiilor lor Python folosind diverse tehnici, cum ar fi selectarea structurilor de date adecvate, valorificarea funcțiilor încorporate, reducerea buclelor suplimentare și gestionarea eficientă a memoriei. Benchmarking-ul și profilarea continuă ar trebui să direcționeze eforturile de optimizare, asigurându-se că progresele codului corespund cerințelor de performanță din lumea reală. Pentru a garanta succesul proiectului pe termen lung și pentru a reduce șansa de a introduce noi probleme, optimizarea codului ar trebui echilibrată în mod constant cu obiectivele de lizibilitate și întreținere a codului.