Jest to konieczne podczas wykonywania zadania wymagającego przetwarzania kilku działań lub danych. Dlatego też wymiana i ulepszenie niektórych nieefektywnych bloków kodu i funkcjonalności może dać niesamowite rezultaty, takie jak poniższe:
- Zwiększ wydajność aplikacji
- Twórz czytelny i zorganizowany kod
- Uprość monitorowanie błędów i debugowanie
- Oszczędzaj znaczną moc obliczeniową i tak dalej
Profiluj swój kod
Zanim zaczniemy optymalizować, konieczne jest zidentyfikowanie części kodu projektu, które ją spowalniają. Techniki profilowania w Pythonie obejmują pakiety cProfile i profile. Korzystaj z takich narzędzi, aby ocenić szybkość wykonywania określonych funkcji i wierszy kodu. Moduł cProfile generuje raport zawierający szczegółowe informacje o czasie działania każdej funkcji skryptu. Ten raport może nam pomóc znaleźć funkcje, które działają wolno, co umożliwi nam ich ulepszenie.
Fragment kodu:
import cProfil Jak cP
def obliczSuma ( numer wejściowy ) :
suma_liczb_wejściowych = 0
chwila numer wejściowy > 0 :
suma_liczb_wejściowych + = numer wejściowy% 10
numer wejściowy // = 10
wydrukować ( „Suma wszystkich cyfr w numerze wejściowym to: 'suma_of_input_numbers'' )
powrót suma_liczb_wejściowych
def główna_funkcja ( ) :
cP. uruchomić ( „oblicz sumę (9876543789)” )
Jeśli __nazwa__ == '__główny__' :
główna_funkcja ( )
Program wykonuje w sumie pięć wywołań funkcji, jak widać w pierwszym wierszu wyniku. Szczegóły każdego wywołania funkcji są pokazane w kilku kolejnych wierszach, łącznie z liczbą wywołań funkcji, całkowitym czasem trwania funkcji, czasem trwania wywołania oraz całkowitą ilością czasu w funkcji (w tym wszystkie funkcje, które on wywołuje).
Dodatkowo program drukuje raport na ekranie zachęty, który pokazuje, że program kończy czas wykonania wszystkich swoich zadań w ciągu 0,000 sekundy. To pokazuje, jak szybki jest program.
Wybierz odpowiednią strukturę danych
Charakterystyka wydajności zależy od struktury danych. W szczególności słowniki umożliwiają szybsze wyszukiwanie niż listy dotyczące przechowywania ogólnego. Wybierz strukturę danych, która jest najbardziej odpowiednia dla operacji, które przeprowadzimy na Twoich danych, jeśli je znasz. Poniższy przykład bada skuteczność różnych struktur danych dla identycznego procesu, aby określić, czy element w strukturze danych jest obecny.
Oceniamy czas potrzebny na sprawdzenie, czy element występuje w każdej strukturze danych — liście, zestawie i słowniku — i porównujemy je.
OptymalizujDataType.py:
import Czas Jak ttimport losowy Jak rndobj
# Wygeneruj listę liczb całkowitych
lista_danych_losowych = [ rndobj. randek ( 1 , 10000 ) Do _ W zakres ( 10000 ) ]
# Utwórz zestaw z tych samych danych
losowy_zestaw_danych = ustawić ( lista_danych_losowych )
# Utwórz słownik z tymi samymi danymi co klucze
obj_DataDictionary = { na jednego: Nic Do na jednego W lista_danych_losowych }
# Element do wyszukania (istnieje w danych)
losowy_numer_do_znalezienia = rndobj. wybór ( lista_danych_losowych )
# Zmierz czas, aby sprawdzić członkostwo na liście
czas_listy = tt. Czas ( lambda : losowy_numer_do_znalezienia W lista_danych_losowych , numer = 1000 )
# Zmierz czas, aby sprawdzić przynależność do zbioru
set_time = tt. Czas ( lambda : losowy_numer_do_znalezienia W losowy_zestaw_danych , numer = 1000 )
# Zmierz czas, aby sprawdzić członkostwo w słowniku
dykt_czas = tt. Czas ( lambda : losowy_numer_do_znalezienia W obj_DataDictionary , numer = 1000 )
wydrukować ( F „Czas sprawdzania członkostwa na liście: {list_time:.6f} sekund” )
wydrukować ( F „Ustaw czas sprawdzania członkostwa: {set_time:.6f} sekund” )
wydrukować ( F „Czas sprawdzania członkostwa w słowniku: {dict_time:.6f} sekund” )
Ten kod porównuje wydajność list, zestawów i słowników podczas sprawdzania członkostwa. Ogólnie rzecz biorąc, zbiory i słowniki są znacznie szybsze niż listy do testów członkostwa, ponieważ korzystają z wyszukiwań opartych na skrótach, więc ich średnia złożoność czasowa wynosi O(1). Z drugiej strony listy muszą przeprowadzać wyszukiwania liniowe, co skutkuje testami członkostwa ze złożonością czasową O(n).
Używaj funkcji wbudowanych zamiast pętli
Liczne wbudowane funkcje lub metody w Pythonie mogą służyć do wykonywania typowych zadań, takich jak filtrowanie, sortowanie i mapowanie. Używanie tych procedur zamiast tworzenia własnych pętli pomaga przyspieszyć kod, ponieważ często są one zoptymalizowane pod kątem wydajności.
Zbudujmy przykładowy kod, aby porównać wydajność tworzenia pętli niestandardowych przy użyciu wbudowanych funkcji dla typowych zadań (takich jak map(), filter() i sorted()). Ocenimy skuteczność różnych metod mapowania, filtrowania i sortowania.
WbudowanyInFunctions.py:
import Czas Jak tt# Przykładowa lista liczb_list
lista_numerów = lista ( zakres ( 1 , 10000 ) )
# Funkcja do kwadratu liczb_listy za pomocą pętli
def Square_using_loop ( lista_numerów ) :
wynik_kwadratowy = [ ]
Do na jednego W lista_numerów:
wynik_kwadratowy. dodać ( na jednego ** 2 )
powrót wynik_kwadratowy
# Funkcja filtrowania listy parzystej za pomocą pętli
def filter_even_using_loop ( lista_numerów ) :
wynik_filtra = [ ]
Do na jednego W lista_numerów:
Jeśli na jednego % 2 == 0 :
wynik_filtra. dodać ( na jednego )
powrót wynik_filtra
# Funkcja sortowania listy_numerów za pomocą pętli
def sort_using_loop ( lista_numerów ) :
powrót posortowane ( lista_numerów )
# Zmierz czas kwadratury liczb_listy za pomocą map()
czas_mapy = tt. Czas ( lambda : lista ( mapa ( lambda x: x ** 2 , lista_numerów ) ) , numer = 1000 )
# Zmierz czas filtrowania listy parzystej za pomocą filter()
czas_filtrowania = tt. Czas ( lambda : lista ( filtr ( lambda x: x% 2 == 0 , lista_numerów ) ) , numer = 1000 )
# Zmierz czas sortowania listy_numerów za pomocą sorted()
sortowany_czas = tt. Czas ( lambda : posortowane ( lista_numerów ) , numer = 1000 )
# Zmierz czas kwadratury liczb_listy za pomocą pętli
pętla_mapa_czas = tt. Czas ( lambda : kwadrat_używanie_pętli ( lista_numerów ) , numer = 1000 )
# Zmierz czas filtrowania listy parzystej za pomocą pętli
czas_filtrowania_pętli = tt. Czas ( lambda : filter_even_using_loop ( lista_numerów ) , numer = 1000 )
# Zmierz czas sortowania listy_numerów za pomocą pętli
pętla_sortowany_czas = tt. Czas ( lambda : sort_using_loop ( lista_numerów ) , numer = 1000 )
wydrukować ( „Lista numerów zawiera 10000 elementów” )
wydrukować ( F „Czas mapy(): {map_time:.6f} sekund” )
wydrukować ( F „Czas filtrowania(): {filter_time:.6f} sekund” )
wydrukować ( F „Czas sortowania(): {sorted_time:.6f} sekund” )
wydrukować ( F „Czas pętli (mapy): {loop_map_time:.6f} sekund” )
wydrukować ( F „Czas pętli (filtrowania): {loop_filter_time:.6f} sekund” )
wydrukować ( F „Czas pętli (posortowany): {loop_sorted_time:.6f} sekund” )
Prawdopodobnie zaobserwujemy, że wbudowane funkcje (map(), filter() i sorted()) są szybsze niż niestandardowe pętle dla tych typowych zadań. Wbudowane funkcje w Pythonie oferują bardziej zwięzłe i zrozumiałe podejście do wykonywania tych zadań i są wysoce zoptymalizowane pod kątem wydajności.
Optymalizuj pętle
Jeśli konieczne jest pisanie pętli, istnieje kilka technik, które możemy zastosować, aby je przyspieszyć. Ogólnie rzecz biorąc, pętla range() jest szybsza niż iteracja wstecz. Dzieje się tak, ponieważ funkcja range() generuje iterator bez odwracania listy, co w przypadku długich list może być kosztowną operacją. Dodatkowo, ponieważ funkcja range() nie tworzy nowej listy w pamięci, zużywa mniej pamięci.
ZoptymalizujLoop.py:
import Czas Jak tt# Przykładowa lista liczb_list
lista_numerów = lista ( zakres ( 1 , 100 000 ) )
# Funkcja umożliwiająca iterację po liście w odwrotnej kolejności
def pętla_odwrotna_iteracja ( ) :
wynik_odwrotny = [ ]
Do J W zakres ( tylko ( lista_numerów ) - 1 , - 1 , - 1 ) :
wynik_odwrotny. dodać ( lista_numerów [ J ] )
powrót wynik_odwrotny
# Funkcja do iteracji po liście za pomocą funkcji range()
def iteracja_zakresu pętli ( ) :
wynik_zakres = [ ]
Do k W zakres ( tylko ( lista_numerów ) ) :
wynik_zakres. dodać ( lista_numerów [ k ] )
powrót wynik_zakres
# Zmierz czas potrzebny na wykonanie iteracji odwrotnej
czas_odwrotny = tt. Czas ( pętla_odwrotna_iteracja , numer = 1000 )
# Zmierz czas potrzebny na wykonanie iteracji zakresu
zakres_czas = tt. Czas ( iteracja_zakresu pętli , numer = 1000 )
wydrukować ( „Lista numerów zawiera 100 000 rekordów” )
wydrukować ( F „Czas odwrotnej iteracji: {reverse_time:.6f} sekund” )
wydrukować ( F „Czas iteracji zakresu: {range_time:.6f} sekund” )
Unikaj niepotrzebnych wywołań funkcji
Każde wywołanie funkcji wiąże się z pewnym obciążeniem. Kod działa szybciej, jeśli unika się niepotrzebnych wywołań funkcji. Na przykład zamiast wielokrotnie wykonywać funkcję obliczającą wartość, spróbuj zapisać wynik obliczenia w zmiennej i użyć go.
Narzędzia do profilowania
Aby dowiedzieć się więcej o wydajności Twojego kodu, oprócz wbudowanego profilowania, możemy skorzystać z zewnętrznych pakietów profilowania, takich jak cProfile, Pyflame lub SnakeViz.
Wyniki w pamięci podręcznej
Jeśli nasz kod musi wykonywać kosztowne obliczenia, możemy rozważyć buforowanie wyników, aby zaoszczędzić czas.
Refaktoryzacja kodu
Refaktoryzacja kodu, aby był łatwiejszy do odczytania i utrzymania, jest czasami niezbędną częścią jego optymalizacji. Szybszy program może być również czystszy.
Skorzystaj z kompilacji just-in-time (JIT)
Biblioteki takie jak PyPy lub Numba mogą zapewnić kompilację JIT, która może znacznie przyspieszyć niektóre typy kodu Pythona.
Uaktualnij Pythona
Upewnij się, że używasz najnowszej wersji Pythona, ponieważ nowsze wersje często zawierają ulepszenia wydajności.
Równoległość i współbieżność
W przypadku procesów, które można zrównoleglić, przeanalizuj techniki równoległe i synchronizacyjne, takie jak przetwarzanie wieloprocesowe, wątki lub asyncio.
Pamiętaj, że benchmarking i profilowanie powinny być głównymi motorami optymalizacji. Skoncentruj się na ulepszaniu obszarów naszego kodu, które mają największy wpływ na wydajność i stale testuj wprowadzone ulepszenia, aby mieć pewność, że przyniosą oczekiwane efekty bez wprowadzania kolejnych defektów.
Wniosek
Podsumowując, optymalizacja kodu w Pythonie ma kluczowe znaczenie dla poprawy wydajności i efektywności wykorzystania zasobów. Programiści mogą znacznie zwiększyć szybkość wykonywania i responsywność swoich aplikacji w języku Python, korzystając z różnych technik, takich jak wybór odpowiednich struktur danych, wykorzystanie wbudowanych funkcji, ograniczenie dodatkowych pętli i efektywne zarządzanie pamięcią. Ciągłe testy porównawcze i profilowanie powinny kierować wysiłkami optymalizacyjnymi, zapewniając, że ulepszenia kodu odpowiadają rzeczywistym wymaganiom wydajnościowym. Aby zagwarantować długoterminowy sukces projektu i zmniejszyć ryzyko wprowadzenia nowych problemów, optymalizacja kodu powinna być stale równoważona celami czytelności i łatwości konserwacji kodu.