Použití Python Timeit k načasování vašeho kódu

V tomto návodu se podíváme na to, jak pracovat s funkcí timeit z modulu timeit v Pythonu. Ukážeme si, jak měřit dobu běhu jednoduchých výrazů a funkcí.

Měření rychlosti kódu vám pomůže zjistit, jak dlouho trvá provedení určité části programu a také odhalit místa, která je vhodné optimalizovat.

Začneme tím, že si vysvětlíme syntaxi funkce timeit. Následně si ukážeme praktické příklady, jak ji využít k měření doby provádění bloků kódu a funkcí ve vašem Python projektu. Pusťme se do toho.

Jak používat funkci timeit v Pythonu

Modul timeit je součástí standardní knihovny Pythonu, takže ho stačí pouze importovat:

import timeit

Následuje syntaxe pro použití funkce timeit z modulu timeit:

timeit.timeit(stmt, setup, number)

Kde:

  • stmt představuje blok kódu, jehož dobu provádění chceme změřit. Může to být jednoduchý Python řetězec, víceřádkový řetězec, nebo název volatelného objektu.
  • setup, jak název napovídá, je blok kódu, který se provede pouze jednou, typicky jako příprava před spuštěním stmt. Například pokud měříte dobu vytváření NumPy pole, tak import knihovny NumPy bude setup kód a samotné vytvoření pole bude stmt.
  • number je parametr, který určuje, kolikrát se má stmt provést. Výchozí hodnota je 1 milion (1 000 000), ale můžete nastavit libovolnou jinou hodnotu.

Nyní, když známe syntaxi funkce timeit(), můžeme se podívat na praktické příklady.

Měření doby běhu jednoduchých výrazů v Pythonu

V této sekci se budeme zabývat měřením doby provádění jednoduchých Python výrazů pomocí funkce timeit.

Spusťte Python REPL a vyzkoušejte si následující příklady. Zde měříme dobu provádění umocňování a celočíselného dělení pro 10 000 a 100 000 opakování.

Je důležité si všimnout, že kód pro měření předáváme jako řetězec a k oddělení jednotlivých výrazů používáme středník.

>>> import timeit
>>> timeit.timeit('3**4;3//4',number=10000)
0.0004020999999738706

>>> timeit.timeit('3**4;3//4',number=100000)
0.0013780000000451764

Spuštění timeit z příkazové řádky

Funkci timeit můžeme použít i z příkazové řádky. Ekvivalentní volání z příkazové řádky je následující:

$ python -m timeit -n [number] -s [setup] [stmt]
  • python -m timeit indikuje, že spouštíme timeit jako hlavní modul.
  • -n je parametr příkazové řádky, který specifikuje, kolikrát se má kód spustit. Je to ekvivalent argumentu number ve funkci timeit().
  • S parametrem -s definujeme kód pro nastavení prostředí.

Zde je přepsaný předchozí příklad pomocí ekvivalentu z příkazové řádky:

$ python -m timeit -n 100000 '3**4;3//4'
100000 loops, best of 5: 35.8 nsec per loop

V tomto příkladu měříme dobu provádění vestavěné funkce len(). Inicializace řetězce je kód setup, který předáváme pomocí parametru -s.

$ python -m timeit -n 100000 -s "string_1 = 'coding'" 'len(string_1)'
100000 loops, best of 5: 239 nsec per loop

Ve výstupu si můžeme všimnout, že se nám zobrazuje doba běhu nejlepšího z 5 opakování. Co to znamená? Když spustíte timeit z příkazové řádky, parametr -r (počet opakování) je implicitně nastaven na 5. To znamená, že se kód stmt pro zadaný počet opakování spustí pětkrát a vrátí se nejlepší čas provedení.

Analýza metod pro obrácení řetězce pomocí timeit

Při práci s řetězci v Pythonu je často potřeba obrátit jejich pořadí. Dva nejběžnější přístupy k obrácení řetězců jsou:

  • Použití řezů (slicing) řetězce.
  • Použití funkce reversed() a metody join().

Obrácení řetězce pomocí řezů

Pojďme se podívat, jak funguje krájení řetězců a jak ho můžete použít pro obrácení řetězce v Pythonu. Syntaxí some_string[start:stop] získáme část řetězce začínající na indexu start a končící na indexu stop-1. Zkusme si příklad.

Mějme řetězec ‚Python‘. Tento řetězec má délku 6 a seznam indexů je 0, 1, 2 až 5.

>>> string_1 = 'Python'

Když zadáme počáteční i koncovou hodnotu, získáme řez, který sahá od počátku až do konce-1. Proto string_1[1:4] vrátí ‚yth‘.

>>> string_1 = 'Python'
>>> string_1[1:4]
'yth'

Pokud nezadáme počáteční hodnotu, použije se výchozí hodnota 0 a řez začíná od indexu nula až do indexu stop – 1.

Zde je koncová hodnota 3, takže řez začíná na indexu 0 a jde až k indexu 2.

>>> string_1[:3]
'Pyt'

Pokud nezahrneme koncový index, řez začne od počátečního indexu (1) a sahá až na konec řetězce.

>>> string_1[1:]
'ython'

Pokud vynecháme počáteční i koncovou hodnotu, získáme kopii celého řetězce.

>>> string_1[::]
'Python'

Vytvořme řez s hodnotou kroku. Nastavíme start, stop a krok na 1, 5 a 2. Získáme část řetězce začínající na 1 až do 4 (kromě koncového bodu 5), obsahující každý druhý znak.

>>> string_1[1:5:2]
'yh'

Když použijeme záporný krok, můžeme získat řez začínající od konce řetězce. S krokem nastaveným na -2, string_1[5:2:-2] nám dá následující výsledek:

>>> string_1[5:2:-2]
'nh'

Pro získání obrácené kopie řetězce vynecháme počáteční a koncové hodnoty a nastavíme krok na -1, jak je ukázáno níže:

>>> string_1[::-1]
'nohtyP'

Ve zkratce: string[::-1] vrací obrácenou kopii řetězce.

Obrácení řetězců s vestavěnými funkcemi a metodami řetězců

Vestavěná funkce reversed() v Pythonu vrací iterátor, který prochází prvky řetězce v opačném pořadí.

>>> string_1 = 'Python'
>>> reversed(string_1)
<reversed object at 0x00BEAF70>

Můžeme tedy iterovat pomocí smyčky for přes tento iterátor a získat tak přístup k jednotlivým znakům v opačném pořadí:

for char in reversed(string_1):
    print(char)
# Výstup:
n
o
h
t
y
P

Dále můžeme volat metodu join() na tomto iterátoru se syntaxí: .join(reversed(some_string)).

Následující úryvek kódu ukazuje několik příkladů, kde je oddělovačem spojovník a mezera.

>>> '-'.join(reversed(string1))
'n-o-h-t-y-P'
>>> ' '.join(reversed(string1))
'n o h t y P'

Pokud nechceme oddělovač, nastavíme ho na prázdný řetězec, abychom získali obrácenou kopii řetězce:

>>> ''.join(reversed(string1))
'nohtyP'

Použitím .join(reversed(some_string)) získáme obrácenou kopii řetězce.

Porovnání časů provádění pomocí timeit

Zatím jsme se naučili dva způsoby, jak obrátit pořadí znaků v řetězci. Ale který z nich je rychlejší? Zkusme to zjistit.

V předchozím příkladu, kde jsme měřili dobu běhu jednoduchých výrazů v Pythonu, jsme nepoužili žádný setup kód. Zde obrácení řetězce provádíme. Zatímco operace obrácení řetězce se spustí tolikrát, kolikrát udává parametr number, setup kód, což je inicializace řetězce, se provede pouze jednou.

>>> import timeit
>>> timeit.timeit(stmt="string_1[::-1]", setup = "string_1 = 'Python'", number = 100000)
0.04951830000001678
>>> timeit.timeit(stmt = "''.join(reversed(string_1))", setup = "string_1 = 'Python'", number = 100000)
0.12858760000000302

Pro stejný počet opakování je metoda řezů rychlejší než použití join() a reversed().

Měření doby běhu Python funkcí pomocí timeit

V této části se podíváme na to, jak měřit dobu provádění Python funkcí pomocí funkce timeit. Mějme funkci hasDigit(), která pro daný seznam řetězců vrátí seznam těch, které obsahují alespoň jednu číslici.

def hasDigit(somelist):
     str_with_digit = []
     for string in somelist:
         check_char = [char.isdigit() for char in string]
         if any(check_char):
            str_with_digit.append(string)
     return str_with_digit

Nyní bychom rádi změřili dobu provádění této funkce pomocí timeit.

Nejprve identifikujeme kód, který chceme měřit (stmt). V tomto případě je to volání funkce hasDigit() s argumentem seznamu řetězců. Dále definujeme setup kód. Tušíte, jak by měl vypadat?

Aby bylo volání funkce úspěšné, měl by setup kód obsahovat následující:

  • Definice funkce hasDigit().
  • Inicializace seznamu řetězců.

Definujeme setup kód v řetězci, jak je ukázáno níže:

setup = """
def hasDigit(somelist):
    str_with_digit = []
    for string in somelist:
      check_char = [char.isdigit() for char in string]
      if any(check_char):
        str_with_digit.append(string)
    return str_with_digit
thislist=['puffin3','7frost','blue']
     """

Nyní můžeme použít funkci timeit a získat dobu provádění funkce hasDigit() pro 100 000 spuštění.

import timeit
timeit.timeit('hasDigit(thislist)',setup=setup,number=100000)
# Výstup:
0.2810094920000097

Závěr

Naučili jste se, jak používat funkci timeit v Pythonu pro měření doby běhu výrazů, funkcí a dalších objektů. To vám může pomoci porovnávat váš kód, analyzovat dobu běhu různých implementací stejné funkcionality, a další.

Pojďme si shrnout, co jsme se naučili v tomto návodu. Funkci timeit() můžete volat se syntaxí timeit.timeit(stmt=…,setup=…,number=…). Alternativně můžete timeit spustit z příkazové řádky pro měření malých úryvků kódu.

Jako další krok si můžete prozkoumat další Python balíčky pro profilování, jako jsou line-profiler a memprofiler, pro profilování kódu z hlediska času i paměti.

Dále se můžete naučit, jak vypočítat časový rozdíl v Pythonu.