Obsah
1. Detekce velikosti hodnot uložených v operační paměti a spravovaných interpretrem Pythonu
2. Standardní funkce getsizeof z balíčku sys
3. Zjištění velikosti hodnot standardních skalárních datových typů Pythonu
4. Velikosti objektů představujících řetězce
5. Zjištění velikosti hodnot uložených do standardních kolekcí Pythonu
6. Velikosti skalárních hodnot i kolekcí
7. Velikosti funkcí v operační paměti zjišťované pomocí getsizeof
8. Velikosti tříd a objektů zjišťované pomocí getsizeof
10. Funkce poskytované balíčkem pympler.asizeof
11. Funkce asizeof.asizeof pro zjištění velikosti hodnot v operační paměti
12. Zjištění a výpis velikosti skalárních hodnot i kolekcí
13. Zobrazení podrobnější statistiky o velikostech hodnot
14. Získání skutečné velikosti funkcí, tříd a objektů uložených v operační paměti
16. Přístup k informacím o objektech uložených na haldě (heap)
17. Proč se tedy liší velikosti hodnot True a False?
18. Způsob uložení celočíselných hodnot Pythonu
19. Repositář s demonstračními příklady
1. Detekce velikosti hodnot uložených v operační paměti a spravovaných interpretrem Pythonu
Programovací jazyk Python se v prvních letech své existence používal především pro tvorbu pomocných systémových a jiných skriptů; jednalo se tedy o (čitelnější) náhradu za Perl, BASH (či jiný v té době existující shell) a AWK. Ovšem v současnosti se Python používá i v mnoha dalších oblastech informatiky, mnohdy i pro tvorbu rozsáhlých aplikací a služeb. A právě ve chvíli, kdy se Python nasazuje i ve velkých aplikacích popř. pokud se používá pro zpracování dat s velkým objemem, se často objevují požadavky na snížení paměťové náročnosti těchto systémů.
Z tohoto důvodu je vhodné alespoň zhruba vědět, jak velké oblasti paměti jsou obsazeny jednotlivými hodnotami, které se v aplikacích psaných v Pythonu používají. Jak však zjistit velikost objektů (resp. obecně řečeno velikost hodnot) uložených v RAM? K tomuto účelu slouží jedna funkce ze standardní knihovny a taktéž další pomocné balíčky, s nimiž se postupně seznámíme.
2. Standardní funkce getsizeof z balíčku sys
V případě, že postačuje zjistit pouze přibližné nároky jednotlivých objektů (resp. přesněji řečeno jejich hodnot) na dostupnou kapacitu operační paměti, není nutné instalovat žádné dodatečné balíčky, protože podobnou funkci nabízí i funkce getsizeof ze standardního balíčku sys:
from sys import getsizeof help(getsizeof)
Nápověda k této funkci je velmi stručná a neobsahuje například informaci, zda vrácená velikost (v bajtech) je skutečnou velikostí objektu nebo se vrací velikost bloku paměti alokované pro zkoumaný objekt. To totiž není totéž – už jen kvůli zarovnání paměťových bloků:
Help on built-in function getsizeof in module sys: getsizeof(...) getsizeof(object [, default]) -> int Return the size of object in bytes.
3. Zjištění velikosti hodnot standardních skalárních datových typů Pythonu
Způsob použití výše zmíněné funkce sys si můžeme otestovat na skriptu, v němž prozkoumáme velikost hodnot základních skalárních datových typů programovacího jazyka Python. Bude se konkrétně jednat o celá čísla (tedy long v Pythonu 3), numerické hodnoty s plovoucí řádovou čárkou, pravdivostní hodnoty, hodnotu None a o řetězce (které jsou kupodivu taktéž považovány za skalární hodnoty). Pro zajímavost jsme do skriptu přidali ještě hodnoty posledního skalárního typu – komplexních čísel:
from sys import getsizeof def print_sizeof(value): typename = "{:8}".format(type(value).__name__) print(getsizeof(value), "\t", typename, "\t", value) print_sizeof(0) print_sizeof(1) print_sizeof(42) print_sizeof(2<<30) print_sizeof(2<<60) print() print_sizeof(1.0) print_sizeof(3.1415) print() print_sizeof(1+2j) print_sizeof(1.2+3.4j) print() print_sizeof(True) print_sizeof(False) print_sizeof(None) print() print_sizeof("") print_sizeof("f") print_sizeof("fo") print_sizeof("foo") print_sizeof("foo bar") print_sizeof("foo bar baz") print_sizeof("foo bar baz xyz") print_sizeof("foo bar baz xyzzy")
Zajímavé bude zjistit, jaké velikosti se vlastně vrací (viz první sloupec):
24 int 0 28 int 1 28 int 42 32 int 2147483648 36 int 2305843009213693952 24 float 1.0 24 float 3.1415 32 complex (1+2j) 32 complex (1.2+3.4j) 28 bool True 24 bool False 16 NoneType None 49 str 50 str f 51 str fo 52 str foo 56 str foo bar 60 str foo bar baz 64 str foo bar baz xyz 66 str foo bar baz xyzzy
4. Velikosti objektů představujících řetězce
Nejjednodušší je situace u řetězců, protože základní velikost objektu typu řetězec (přesněji prázdný řetězec) je 49 bajtů a každý další přidaný znak znamená, že se velikost objektu zvýší o jeden až čtyři bajty, v závislosti na konkrétních znacích uložených v řetězci.
V programovacím jazyku Python verze totiž 3.3 došlo k poměrně významné změně, která se týká způsobu interního uložení řetězců. Autoři Pythonu si totiž uvědomili, že na jednu stranu je sice důležité a velmi žádoucí podporovat Unicode (a to celé Unicode, tedy žádný subset typu BMP, vzhledem k normě GB 18030) ovšem v mnoha případech to vede k tomu, že se do operační paměti ukládá až čtyřikrát větší množství dat, než je skutečně nutné, protože mnoho řetězců používaných v každodenní praxi obsahuje pouze ASCII znaky (příkladem mohou být URL). Navíc větší množství dat uložených v paměti znamená, že se při manipulaci s nimi bude hůře využívat procesorová cache. Proto došlo v rámci PEP 393 k takové úpravě, která zajistí možnost uložení řetězců ve třech formátech, což je naznačeno v tabulce:
Šířka znaku | Kódování | Prefix při zápisu kódu |
---|---|---|
1 bajt | Latin-1 | \x |
2 bajty | UCS-2 | \u |
4 bajty | UCS-4 | \U |
Tyto změny by měly být pro programátory i uživatele zcela transparentní, takže by se nemělo stát, že by například do řetězce původně uloženého s kódováním Latin-1 (nadmnožina ASCII) nešel uložit například znak v azbuce – ostatně řetězce jsou v Pythonu neměnitelné, takže se konverze provede v rámci prováděné operace automaticky.
Podívejme se nyní, jak je výběr formátu pro uložení řetězce prováděn při interpretaci řetězcového literálu. Nejprve importujeme modul sys, který nabízí funkci getsizeof():
>>> import sys
Zjistíme velikost objektu reprezentujícího prázdný řetězec. Tato velikost se může lišit podle verze Pythonu a použité architektury, nás však budou zajímat rozdíly oproti této hodnotě:
>>> sys.getsizeof("") 49
Zjistíme velikost objektu řetězce s jedním ASCII znakem (měla by být o jedničku vyšší, než hodnota předchozí) a taktéž velikost objektu řetězce s jedenácti znaky (bude se lišit o deset bajtů oproti hodnotě předchozí). Výsledek je zřejmý – každý znak je v tomto případě reprezentován jediným bajtem:
>>> sys.getsizeof("e") 50 >>> sys.getsizeof("e 123456789") 60
Nyní vytvoříme řetězec s ne-ASCII znakem. Velikost příslušného objektu se zvětší (opět nás však bude zajímat rozdíl oproti této velikosti, ne její absolutní hodnota):
>>> sys.getsizeof("ě") 76
Dále vypočteme velikost řetězce s ne-ASCII znakem, po němž následuje deset ASCII znaků. Vidíme, že každý znak je uložen ve dvou bajtech – prvním znakem byl určen interní formát řetězce:
>>> sys.getsizeof("ě 123456789") 96
Zkusme si nyní vytvořit řetězec s jediným znakem, který nepatří do BPM, tedy ho nelze reprezentovat v UCS-2. Posléze k tomuto znaku přidáme dalších deset znaků a snadno zjistíme, že v tomto případě je každý znak reprezentován čtyřmi bajty ((120–80)/10):
>>> sys.getsizeof("\U0001ffff") 80 >>> sys.getsizeof("\U0001ffff 123456789") 120
Jen pro zajímavost se můžeme podívat, jak celý objekt s řetězcem vypadá. U ASCII řetězců:
>>> bytearray((ctypes.c_byte*sys.getsizeof("Hello world!")).from_address(id("Hello world!"))) bytearray(b'\x02\x00\x00\x00\x00\x00\x00\x00\x00\x89\x96\x00 \x00\x00\x00\x00\x0c\x00\x00\x00\x00\x00\x00\x00\x17L\xc6c\x01 \xc0\x08a\xe4\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00 \x00\x00\x00Hello world!\x00')
U řetězců reprezentovaných v UCS-2:
>>> bytearray((ctypes.c_byte*sys.getsizeof("ěščřžýáíéúů")).from_address(id("ěščřžýáíéúů"))) bytearray(b'\x02\x00\x00\x00\x00\x00\x00\x00\x00\x89\x96\x00 \x00\x00\x00\x00\x0b\x00\x00\x00\x00\x00\x00\x00\x98\xd8J\xd9 \xd5\xb7\xd0\x9d\xa8\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x1b \x01a\x01\r\x01Y\x01~\x01\xfd\x00\xe1\x00\xed\x00\xe9\x00\xfa \x00o\x01\x00\x00')
5. Zjištění velikosti hodnot uložených do standardních kolekcí Pythonu
V dalším kroku použijeme funkci getsizeof pro zjištění velikosti kolekcí, tedy n-tic, seznamů a asociativních polí (můžet si do příkladu přidat i množiny):
from sys import getsizeof def print_sizeof(value): typename = "{:8}".format(type(value).__name__) print(getsizeof(value), "\t", typename, "\t", value) print_sizeof(()) print_sizeof((1,)) print_sizeof((1, 2)) print_sizeof((1, 2, 3)) print_sizeof((1, 2, 3, 4)) print_sizeof((1, 2, 3, 4, 5)) print_sizeof((1, 2, 3, 4, 5, 6)) print() print_sizeof([]) print_sizeof([1]) print_sizeof([1, 2]) print_sizeof([1, 2, 3]) print_sizeof([1, 2, 3, 4]) print_sizeof([1, 2, 3, 4, 5]) print_sizeof([1, 2, 3, 4, 5, 6]) print() print_sizeof({}) print_sizeof({1:1}) print_sizeof({1:1, 2:2}) print_sizeof({1:1, 2:2, 3:3}) print_sizeof({1:1, 2:2, 3:3, 4:4}) print_sizeof({1:1, 2:2, 3:3, 4:4, 5:5}) print_sizeof({1:1, 2:2, 3:3, 4:4, 5:5, 6:6})
Výsledky ukazují, jak se velikosti kolekcí postupně zvětšují společně se zvyšujícím se počtem prvků:
40 tuple () 48 tuple (1,) 56 tuple (1, 2) 64 tuple (1, 2, 3) 72 tuple (1, 2, 3, 4) 80 tuple (1, 2, 3, 4, 5) 88 tuple (1, 2, 3, 4, 5, 6) 56 list [] 64 list [1] 72 list [1, 2] 80 list [1, 2, 3] 88 list [1, 2, 3, 4] 96 list [1, 2, 3, 4, 5] 104 list [1, 2, 3, 4, 5, 7] 64 dict {} 232 dict {1: 1} 232 dict {1: 1, 2: 2} 232 dict {1: 1, 2: 2, 3: 3} 232 dict {1: 1, 2: 2, 3: 3, 4: 4} 232 dict {1: 1, 2: 2, 3: 3, 4: 4, 5: 5} 360 dict {1: 1, 2: 2, 3: 3, 4: 4, 5: 5, 6: 6}
Ukazují se však skutečně velikosti všech hodnot, nebo pouze velikosti referencí uložených v kolekcích (v Pythonu je vše reference)? To zjistíme snadno – zkusíme do kolekce uložit řetězce s milionem znaků:
from sys import getsizeof def print_sizeof(value): typename = "{:8}".format(type(value).__name__) print(getsizeof(value), "\t", typename) print_sizeof((1, 2)) print_sizeof((1, "?" * 1000000)) print() print_sizeof([1, 2]) print_sizeof([1, "?" * 1000000]) print() print_sizeof({1:1, 2:2}) print_sizeof({1:1, 2:"?" * 1000000})
Z výsledků je jasně patrné, že se velikosti samotných hodnot do výsledku nezapočítávají:
56 tuple 56 tuple 72 list 72 list 232 dict 232 dict
6. Velikosti skalárních hodnot i kolekcí
Hodnoty získané výše uvedenými skripty nám pomohly získat alespoň základní představu o tom, jaký objem RAM je potřeba pro uložení skalárních hodnot i prvků do základních kolekcí. Shrnutí platné pro současné verze Pythonu 3 (budoucí verze Pythonu mohou mít odlišné požadavky) je uvedeno v následující tabulce:
Datový typ | Význam | Minimální velikost | Další růst |
---|---|---|---|
long | celé číslo | 28 | +4 bajty pro každou mocninu 230 |
float | číslo s plovoucí řádovou čárkou | 24 | × |
complex | komplexní číslo s plovoucí řádovou čárkou | 32 | × |
bool | True nebo False | 24/28 | × |
NoneType | hodnota none | 16 | × |
string | řetězec | 49 | +1 až 4 bajty pro každý další znak (již známe) |
tuple | n-tice | 40 | +8 bajtů pro každý další prvek n-tice |
list | seznam | 56 | +8 bajtů pro každý další prvek seznamu |
set | množina | 216 | 5 prvků: 728, 19 prvků: 2264, …, 77 prvků: 8408, … |
dict | slovník | 64 | 1 prvek 232, 6 prvků: 360; 22 prvků: 1184; … |
7. Velikosti funkcí v operační paměti zjišťované pomocí getsizeof
Velmi důležitým datovým typem je v programovacím jazyku Python funkce. Podobně jako v případě hodnot dalších typů může být v operační paměti uloženo (obecně) libovolné množství hodnot typu funkce. Vyzkoušejme si tedy, jakou velikost (chápáno jaké množství bajtů) funkce vrací standardní volání sys.getsizeof, a to pro různě definované funkce:
from sys import getsizeof def print_sizeof(value): typename = "{:8}".format(type(value).__name__) print(getsizeof(value), "\t", typename, "\t", value) def foo(): pass def bar(x, y): return x+y def baz(x=0, y=1): print(x) print(y) return x+y print_sizeof(print) print_sizeof(foo) print_sizeof(bar) print_sizeof(baz)
Získané výsledky vypadají následovně:
72 builtin_function_or_method <built-in function print> 136 function <function foo at 0x7fcea02f8160> 136 function <function bar at 0x7fcea02f81f0> 136 function <function baz at 0x7fcea02f8280>
8. Velikosti tříd a objektů zjišťované pomocí getsizeof
Standardní funkce sys.getsizeof může být použita i pro zjištění velikosti tříd (což jsou taktéž hodnoty) a instancí tříd neboli objektů. Opět se podívejme na jednoduchý příklad, v němž tyto hodnoty zjišťujeme:
from sys import getsizeof def print_sizeof(value): typename = "{:8}".format(type(value).__name__) print(getsizeof(value), "\t", typename, "\t", value) class C1: pass class C2: def __init__(self): pass class C3: def __init__(self): pass def foo(self, x): self.x=x def bar(self, y): self.y=y o1 = C1() o2 = C2() o3 = C3() print_sizeof(C1) print_sizeof(o1) print_sizeof(C2) print_sizeof(o2) print_sizeof(C3) print_sizeof(o3) o3.foo(42) print_sizeof(C3) print_sizeof(o3) o3.bar(0) print_sizeof(C3) print_sizeof(o3)
Podívejme se na zjištěné a zobrazené velikosti objektů i tříd:
1064 type <class '__main__.C1'> 48 C1 <__main__.C1 object at 0x7ff7f2e20430> 1064 type <class '__main__.C2'> 48 C2 <__main__.C2 object at 0x7ff7f2e20040> 1064 type <class '__main__.C3'> 48 C3 <__main__.C3 object at 0x7ff7f2e20190> 1064 type <class '__main__.C3'> 48 C3 <__main__.C3 object at 0x7ff7f2e20190> 1064 type <class '__main__.C3'> 48 C3 <__main__.C3 object at 0x7ff7f2e20190>
Popř. lze příklad nepatrně upravit tak, že naplníme atribut objektu tím samým objektem (resp. referencí na ten samý objekt):
from sys import getsizeof def print_sizeof(value): typename = "{:8}".format(type(value).__name__) print(getsizeof(value), "\t", typename, "\t", value) class C4: def __init__(self): pass def foo(self, x): self.x=x o4 = C4() print_sizeof(C4) print_sizeof(o4) o4.foo(o4) print_sizeof(C4) print_sizeof(o4)
Výsledky nyní budou vypadat následovně:
1064 type <class '__main__.C4'> 48 C4 <__main__.C4 object at 0x7f7f2f0d6430> 1064 type <class '__main__.C4'> 48 C4 <__main__.C4 object at 0x7f7f2f0d6430>
Naše znalosti o velikosti hodnot tedy můžeme doplnit takto:
Datový typ | Význam | Minimální velikost | Další růst |
---|---|---|---|
func | funkce | 136 | |
class | definice třídy | 1064 | |
inst | instance třídy | 48 | jako u slovníků pro každý další atribut |
9. Balíček Pympler
Největší předností výše popsané funkce sys.getsizeof je fakt, že se jedná o funkci ze standardní knihovny sys. To mj. znamená, že tuto funkci je možné zavolat prakticky kdykoli a kdekoli, a to bez nutnosti instalace nějakých dalších balíčků. Ovšem tato funkce není příliš dokonalá, protože například neumožňuje zjistit velikost hodnot v operační paměti po zarovnání, protože alokátor paměti alokuje nové bloky vždy na adresách dělitelných nějakou konstantou 4, 8 či 16 (podle systému a architektury) a tudíž vlastně nebudou využity bajty umístěné těsně za koncem bloku v případě, že velikost bloku není dělitelná výše uvedenou konstantou. A navíc má tato standardní funkce problémy se zjištěním velikosti tříd a objektů, protože automaticky nezapočítává velikosti jednotlivých atributů atd.
Existují však i další (ovšem nutno dodat, že již nestandardní) balíčky, které „opravují“ chování funkce sys.getsizeof a navíc nabízí uživatelům i další zajímavou a potenciálně užitečnou funkcionalitu. Tyto balíčky je však pochopitelně nutné nejdříve nainstalovat. Prvním z balíčků, o nichž se dnes alespoň ve stručnosti zmíníme, je balíček nazvaný Pympler. Vzhledem k tomu, že je zaregistrovaný na PyPi, je instalace tohoto balíčku triviální a můžeme ji provést (pro právě přihlášeného uživatele) tímto příkazem:
$ pip3 install --user pympler
Samotná instalace proběhne prakticky okamžitě:
Collecting pympler Downloading Pympler-1.0.1-py3-none-any.whl (164 kB) |████████████████████████████████| 164 kB 1.5 MB/s Installing collected packages: pympler Successfully installed pympler-1.0.1
10. Funkce poskytované balíčkem pympler.asizeof
Samotný Pympler obsahuje několik podbalíčků, z nichž nás v dnešním článku bude zajímat především podbalíček nazvaný pympler.asizeof:
>>> from pympler import asizeof
Podbalíček pympler.asizeof v současné verzi obsahuje devět užitečných funkcí:
>>> help(asizeof)
Help on module pympler.asizeof in pympler: NAME pympler.asizeof DESCRIPTION This module exposes 9 functions and 2 classes to obtain lengths and sizes of Python objects (for Python 3.5 or later). Earlier versions of this module supported Python versions down to Python 2.2. If you are using Python 3.5 or older, please consider downgrading Pympler. **Public Functions** [#unsafe]_ Function **asizeof** calculates the combined (approximate) size in bytes of one or several Python objects. Function **asizesof** returns a tuple containing the (approximate) size in bytes for each given Python object separately. Function **asized** returns for each object an instance of class **Asized** containing all the size information of the object and a tuple with the referents [#refs]_. Functions **basicsize** and **itemsize** return the *basic-* respectively *itemsize* of the given object, both in bytes. For objects as ``array.array``, ``numpy.array``, ``numpy.matrix``, etc. where the item size varies depending on the instance-specific data type, function **itemsize** returns that item size. Function **flatsize** returns the *flat size* of a Python object in bytes defined as the *basic size* plus the *item size* times the *length* of the given object. Function **leng** returns the *length* of an object, like standard function ``len`` but extended for several types. E.g. the **leng** of a multi-precision int (or long) is the number of ``digits`` [#digit]_. The length of most *mutable* sequence objects includes an estimate of the over-allocation and therefore, the **leng** value may differ from the standard ``len`` result. For objects like
V podbalíčku však nalezneme i další více či méně zajímavé hodnoty, které lze vypsat například takto:
from pympler import asizeof for item in dir(asizeof): if item[0] != "_": print(item)
Výsledek:
ABCMeta Asized Asizer Callable Dict List Optional Struct Tuple Types Union Weakref adict array asized asizeof asizesof basicsize calcsize curdir finditer flatsize isbuiltin isclass iscode isframe isfunction ismethod ismodule itemsize leng linesep log named_refs numpy refs stack stat statvfs sys warnings
11. Funkce asizeof.asizeof pro zjištění velikosti hodnot v operační paměti
Nejužitečnější funkce z podbalíčku pympler.asizeof se jmenuje taktéž asizeof. Jedná se o v mnoha ohledech vylepšenou standardní funkci sys.getsizeof, která umožňuje vypočítat skutečné velikosti objektů uložených v operační paměti:
from pympler import asizeof help(asizeof.asizeof)
Tato funkce má několik zajímavých přepínačů, z nichž některé si popíšeme v dalším textu:
Help on function asizeof in module pympler.asizeof: asizeof(*objs, **opts) Return the combined size (in bytes) of all objects passed as positional arguments. The available options and defaults are: *above=0* -- threshold for largest objects stats *align=8* -- size alignment *clip=80* -- clip ``repr()`` strings *code=False* -- incl. (byte)code size *cutoff=10* -- limit large objects or profiles stats *derive=False* -- derive from super type *frames=False* -- ignore stack frame objects *ignored=True* -- ignore certain types *infer=False* -- try to infer types *limit=100* -- recursion limit *stats=0* -- print statistics
12. Zjištění a výpis velikosti skalárních hodnot i kolekcí
Nyní se pokusme upravit skripty pro zjišťování velikostí různých hodnot uložených v operační paměti takovým způsobem, aby se namísto standardní funkce sys-getsizeof volala funkce asizeof z podbalíčku pympler.asizeof. Úprava skriptu je ve skutečnosti minimální, ovšem umožní nám další úpravy (a taktéž výsledky se mohou lišit).
Začneme skriptem, v němž se zjišťují velikosti skalárních hodnot, a to včetně řetězců. Upravená varianta tohoto skriptu bude vypadat následovně:
from pympler import asizeof def print_sizeof(value): typename = "{:8}".format(type(value).__name__) print(asizeof.asizeof(value), "\t", typename, "\t", value) print_sizeof(0) print_sizeof(1) print_sizeof(42) print_sizeof(2<<30) print_sizeof(2<<60) print() print_sizeof(1.0) print_sizeof(3.1415) print() print_sizeof(1+2j) print_sizeof(1.2+3.4j) print() print_sizeof(True) print_sizeof(False) print_sizeof(None) print() print_sizeof("") print_sizeof("f") print_sizeof("fo") print_sizeof("foo") print_sizeof("foo bar") print_sizeof("foo bar baz") print_sizeof("foo bar baz xyz") print_sizeof("foo bar baz xyzzy")
Podívejme se nyní na zjištěné a vypsané výsledky:
24 int 0 32 int 1 32 int 42 32 int 2147483648 40 int 2305843009213693952 24 float 1.0 24 float 3.1415 32 complex (1+2j) 32 complex (1.2+3.4j) 32 bool True 24 bool False 16 NoneType None 56 str 56 str f 56 str fo 56 str foo 56 str foo bar 64 str foo bar baz 64 str foo bar baz xyz 72 str foo bar baz xyzzy
V dalším kroku provedeme úpravu skriptu pro zjištění velikosti kolekcí:
from pympler import asizeof def print_sizeof(value): typename = "{:8}".format(type(value).__name__) print(asizeof.asizeof(value), "\t", typename, "\t", value) print_sizeof(()) print_sizeof((1,)) print_sizeof((1, 2)) print_sizeof((1, 2, 3)) print_sizeof((1, 2, 3, 4)) print_sizeof((1, 2, 3, 4, 5)) print_sizeof((1, 2, 3, 4, 5, 6)) print() print_sizeof([]) print_sizeof([1]) print_sizeof([1, 2]) print_sizeof([1, 2, 3]) print_sizeof([1, 2, 3, 4]) print_sizeof([1, 2, 3, 4, 5]) print_sizeof([1, 2, 3, 4, 5, 6]) print() print_sizeof({}) print_sizeof({1:1}) print_sizeof({1:1, 2:2}) print_sizeof({1:1, 2:2, 3:3}) print_sizeof({1:1, 2:2, 3:3, 4:4}) print_sizeof({1:1, 2:2, 3:3, 4:4, 5:5}) print_sizeof({1:1, 2:2, 3:3, 4:4, 5:5, 6:6})
Opět je zajímavé se podívat na výsledky, které tento skript vypočítal a zobrazil:
40 tuple () 80 tuple (1,) 120 tuple (1, 2) 160 tuple (1, 2, 3) 200 tuple (1, 2, 3, 4) 240 tuple (1, 2, 3, 4, 5) 280 tuple (1, 2, 3, 4, 5, 6) 56 list [] 96 list [1] 136 list [1, 2] 176 list [1, 2, 3] 216 list [1, 2, 3, 4] 256 list [1, 2, 3, 4, 5] 296 list [1, 2, 3, 4, 5, 6] 64 dict {} 264 dict {1: 1} 296 dict {1: 1, 2: 2} 328 dict {1: 1, 2: 2, 3: 3} 360 dict {1: 1, 2: 2, 3: 3, 4: 4} 392 dict {1: 1, 2: 2, 3: 3, 4: 4, 5: 5} 552 dict {1: 1, 2: 2, 3: 3, 4: 4, 5: 5, 6: 6}
Opět se podívejme na příklad s kolekcemi, které obsahují řetězce o délce milionu znaků (a v tomto případě i bajtů):
from pympler import asizeof def print_sizeof(value): typename = "{:8}".format(type(value).__name__) print(asizeof.asizeof(value, code=True), "\t", typename) print_sizeof((1, 2)) print_sizeof((1, "?" * 1000000)) print() print_sizeof([1, 2]) print_sizeof([1, "?" * 1000000]) print() print_sizeof({1:1, 2:2}) print_sizeof({1:1, 2:"?" * 1000000})
Nyní již výsledky, na rozdíl od použití sys.getsizeof, mají praktický význam:
120 tuple 1000144 tuple 136 list 1000160 list 296 dict 1000352 dict
13. Zobrazení podrobnější statistiky o velikostech hodnot
O hodnotách uložených v operační paměti lze získat i další podrobnější informace. To provedeme takovým způsobem, že při volání funkce asizeof kromě reference na hodnotu (objekt) použijeme i pojmenovaný parametr stats nastavený na hodnotu odlišnou od False. Vyzkoušejme si to nejprve pro skalární hodnoty:
from pympler import asizeof def print_sizeof(value): typename = "{:8}".format(type(value).__name__) print(asizeof.asizeof(value, stats=1), "\n", typename, "\t", value) print_sizeof(0) print_sizeof(1) print_sizeof(42) print_sizeof(2<<30) print_sizeof(2<<60) print() print_sizeof(1.0) print_sizeof(3.1415) print() print_sizeof(1+2j) print_sizeof(1.2+3.4j) print() print_sizeof(True) print_sizeof(False) print_sizeof(None) print() print_sizeof("") print_sizeof("f") print_sizeof("fo") print_sizeof("foo") print_sizeof("foo bar") print_sizeof("foo bar baz") print_sizeof("foo bar baz xyz") print_sizeof("foo bar baz xyzzy")
Z výsledků je patrné, že se skutečně zobrazí mnoho doplňujících informací o každé hodnotě, a to včetně informace o zarovnání atd.:
asizeof((0,), stats=1) ... 24 bytes 8 byte aligned 8 byte sizeof(void*) 1 object given 1 object sized 1 object seen 0 objects missed 0 duplicates 24 int 0 asizeof((1,), stats=1) ... 32 bytes 8 byte aligned 8 byte sizeof(void*) 1 object given 1 object sized 1 object seen 0 objects missed 0 duplicates 32 int 1 asizeof((42,), stats=1) ... 32 bytes 8 byte aligned 8 byte sizeof(void*) 1 object given 1 object sized 1 object seen 0 objects missed 0 duplicates 32 int 42 asizeof((2147483648,), stats=1) ... 32 bytes 8 byte aligned 8 byte sizeof(void*) 1 object given 1 object sized 1 object seen 0 objects missed 0 duplicates 32 int 2147483648 asizeof((2305843009213693952,), stats=1) ... 40 bytes 8 byte aligned 8 byte sizeof(void*) 1 object given 1 object sized 1 object seen 0 objects missed 0 duplicates 40 int 2305843009213693952 asizeof((1.0,), stats=1) ... 24 bytes 8 byte aligned 8 byte sizeof(void*) 1 object given 1 object sized 1 object seen 0 objects missed 0 duplicates 24 float 1.0 asizeof((3.1415,), stats=1) ... 24 bytes 8 byte aligned 8 byte sizeof(void*) 1 object given 1 object sized 1 object seen 0 objects missed 0 duplicates 24 float 3.1415 asizeof(((1+2j),), stats=1) ... 32 bytes 8 byte aligned 8 byte sizeof(void*) 1 object given 1 object sized 1 object seen 0 objects missed 0 duplicates 32 complex (1+2j) asizeof(((1.2+3.4j),), stats=1) ... 32 bytes 8 byte aligned 8 byte sizeof(void*) 1 object given 1 object sized 1 object seen 0 objects missed 0 duplicates 32 complex (1.2+3.4j) asizeof((True,), stats=1) ... 32 bytes 8 byte aligned 8 byte sizeof(void*) 1 object given 1 object sized 1 object seen 0 objects missed 0 duplicates 32 bool True asizeof((False,), stats=1) ... 24 bytes 8 byte aligned 8 byte sizeof(void*) 1 object given 1 object sized 1 object seen 0 objects missed 0 duplicates 24 bool False asizeof((None,), stats=1) ... 16 bytes 8 byte aligned 8 byte sizeof(void*) 1 object given 1 object sized 1 object seen 0 objects missed 0 duplicates 16 NoneType None asizeof(('',), stats=1) ... 56 bytes 8 byte aligned 8 byte sizeof(void*) 1 object given 1 object sized 1 object seen 0 objects missed 0 duplicates 56 str asizeof(('f',), stats=1) ... 56 bytes 8 byte aligned 8 byte sizeof(void*) 1 object given 1 object sized 1 object seen 0 objects missed 0 duplicates 56 str f asizeof(('fo',), stats=1) ... 56 bytes 8 byte aligned 8 byte sizeof(void*) 1 object given 1 object sized 1 object seen 0 objects missed 0 duplicates 56 str fo ... ... ... asizeof(('foo bar baz xyzzy',), stats=1) ... 72 bytes 8 byte aligned 8 byte sizeof(void*) 1 object given 1 object sized 1 object seen 0 objects missed 0 duplicates 72 str foo bar baz xyzzy
Podobně můžeme získat informace o kolekcích:
from pympler import asizeof def print_sizeof(value): typename = "{:8}".format(type(value).__name__) print(asizeof.asizeof(value, stats=1), "\t", typename, "\t", value) print_sizeof(()) print_sizeof((1,)) print_sizeof((1, 2)) print_sizeof((1, 2, 3)) print_sizeof((1, 2, 3, 4)) print_sizeof((1, 2, 3, 4, 5)) print_sizeof((1, 2, 3, 4, 5, 6)) print() print_sizeof([]) print_sizeof([1]) print_sizeof([1, 2]) print_sizeof([1, 2, 3]) print_sizeof([1, 2, 3, 4]) print_sizeof([1, 2, 3, 4, 5]) print_sizeof([1, 2, 3, 4, 5, 6]) print() print_sizeof({}) print_sizeof({1:1}) print_sizeof({1:1, 2:2}) print_sizeof({1:1, 2:2, 3:3}) print_sizeof({1:1, 2:2, 3:3, 4:4}) print_sizeof({1:1, 2:2, 3:3, 4:4, 5:5}) print_sizeof({1:1, 2:2, 3:3, 4:4, 5:5, 6:6})
Výsledky (zkráceno, ovšem povšimněte si, že se detekují prvky uložené v kolekcích):
asizeof(((),), stats=1) ... 40 bytes 8 byte aligned 8 byte sizeof(void*) 1 object given 1 object sized 1 object seen 0 objects missed 0 duplicates 1 deepest recursion 40 tuple () ... ... ... asizeof(((1, 2, 3, 4, 5, 6),), stats=1) ... 280 bytes 8 byte aligned 8 byte sizeof(void*) 1 object given 7 objects sized 7 objects seen 0 objects missed 0 duplicates 1 deepest recursion 280 tuple (1, 2, 3, 4, 5, 6) asizeof(([],), stats=1) ... 56 bytes 8 byte aligned 8 byte sizeof(void*) 1 object given 1 object sized 1 object seen 0 objects missed 0 duplicates 1 deepest recursion 56 list [] ... ... ... asizeof(([1, 2, 3, 4, 5, 6],), stats=1) ... 296 bytes 8 byte aligned 8 byte sizeof(void*) 1 object given 7 objects sized 7 objects seen 0 objects missed 0 duplicates 1 deepest recursion 296 list [1, 2, 3, 4, 5, 6] asizeof(({},), stats=1) ... 64 bytes 8 byte aligned 8 byte sizeof(void*) 1 object given 1 object sized 1 object seen 0 objects missed 0 duplicates 1 deepest recursion 64 dict {} ... ... ... asizeof(({1: 1, 2: 2, 3: 3, 4: 4, 5: 5, 6: 6},), stats=1) ... 552 bytes 8 byte aligned 8 byte sizeof(void*) 1 object given 7 objects sized 13 objects seen 0 objects missed 6 duplicates 1 deepest recursion 552 dict {1: 1, 2: 2, 3: 3, 4: 4, 5: 5, 6: 6}
14. Získání skutečné velikosti funkcí, tříd a objektů uložených v operační paměti
Jak jsme si již řekli v úvodní části tohoto článku, vrací standardní funkce sys.getsizeof pro všechny třídy stejnou velikost a totéž do jisté míry platí i pro všechny objekty (tam ovšem již záleží na počtu atributů). Bude tedy zajímavé zjistit, jaké hodnoty vypíše funkce asizeof, která je interně mnohem komplikovanější, než sys.getsizeof.
Začneme výpisem velikosti funkcí:
from pympler import asizeof def print_sizeof(value): typename = "{:8}".format(type(value).__name__) print(asizeof.asizeof(value), "\t", typename, "\t", value) def foo(): pass def bar(x, y): return x+y def baz(x=0, y=1): print(x) print(y) return x+y print_sizeof(print) print_sizeof(foo) print_sizeof(bar) print_sizeof(baz)
Výsledky jsou poněkud překvapivé:
0 builtin_function_or_method <built-in function print> 0 function <function foo at 0x7f3828c4d790> 0 function <function bar at 0x7f3828c4d820> 0 function <function baz at 0x7f3828c4d8b0>
Pro získání skutečné velikosti funkcí je nutné funkci asizeof předat pojmenovaný parametr code nastavený na hodnotu odlišnou od False. Příklad tedy upravíme do následující podoby:
from pympler import asizeof def print_sizeof(value): typename = "{:8}".format(type(value).__name__) print(asizeof.asizeof(value, code=True), "\t", typename, "\t", value) def foo(): pass def bar(x, y): return x+y def baz(x=0, y=1): print(x) print(y) return x+y print_sizeof(print) print_sizeof(foo) print_sizeof(bar) print_sizeof(baz)
S výsledky:
0 builtin_function_or_method <built-in function print> 736 function <function foo at 0x7f3ced833790> 912 function <function bar at 0x7f3ced833820> 1032 function <function baz at 0x7f3ced8338b0>
Podobně můžeme postupovat i při získávání velikostí tříd a objektů – opět je více než vhodné použít parametr code v případě, že nás zajímá skutečná velikost objektu jak s kódem, tak i s atributy:
from pympler import asizeof def print_sizeof(value): typename = "{:8}".format(type(value).__name__) print(asizeof.asizeof(value, code=True), "\t", typename, "\t", value) class C1: pass class C2: def __init__(self): pass class C3: def __init__(self): pass def foo(self, x): self.x=x def bar(self, y): self.y=y o1 = C1() o2 = C2() o3 = C3() print_sizeof(C1) print_sizeof(o1) print_sizeof(C2) print_sizeof(o2) print_sizeof(C3) print_sizeof(o3) o3.foo(42) print_sizeof(C3) print_sizeof(o3) o3.bar(0) print_sizeof(C3) print_sizeof(o3)
Výsledky:
1672 type <class '__main__.C1'> 1824 C1 <__main__.C1 object at 0x7fb416e05430> 2504 type <class '__main__.C2'> 2656 C2 <__main__.C2 object at 0x7fb416e05040> 3832 type <class '__main__.C3'> 3984 C3 <__main__.C3 object at 0x7fb416e05190> 3832 type <class '__main__.C3'> 4016 C3 <__main__.C3 object at 0x7fb416e05190> 3832 type <class '__main__.C3'> 4016 C3 <__main__.C3 object at 0x7fb416e05190>
A konečně si ukažme, se není velkým problémem ani situace, kdy objekt obsahuje referenci na sebe sama:
from pympler import asizeof def print_sizeof(value): typename = "{:8}".format(type(value).__name__) print(asizeof.asizeof(value, code=True), "\t", typename, "\t", value) class C4: def __init__(self): pass def foo(self, x): self.x=x o4 = C4() print_sizeof(C4) print_sizeof(o4) o4.foo(o4) print_sizeof(C4) print_sizeof(o4)
Výsledek:
3184 type <class '__main__.C4'> 3336 C4 <__main__.C4 object at 0x7fc7f42b9430> 3184 type <class '__main__.C4'> 3336 C4 <__main__.C4 object at 0x7fc7f42b9430>
15. Balíček guppy3
Kromě balíčku Pympler, jehož (prozatím velmi malou část) jsme si dnes popsali, ovšem existují i další balíčky, které se používají ve chvílích, kdy je nutné zjistit chování aplikace z hlediska spotřeby operační paměti. Mezi velmi užitečný balíček z této oblasti patří balíček nazvaný guppy, který je již možné využít pro analýzu chování celé aplikace, tedy nejenom pro zjištění velikosti jednotlivých hodnot (navíc typicky nemá programátor přehled o všech hodnotách, které jsou v paměti uloženy, protože některé z těchto hodnot využívá samotný interpret atd.). I tento balíček je dostupný na PyPi, takže je jeho instalace triviální:
$ pip3 install --user guppy3
Collecting guppy3 Downloading guppy3-3.1.2-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl (649 kB) |████████████████████████████████| 649 kB 1.6 MB/s Installing collected packages: guppy3 Successfully installed guppy3-3.1.2
Po instalaci si otestujeme, zda je nově nainstalovaný balíček skutečně dostupný i z interpretru Pythonu (konkrétně se zaměříme na balíček hpy s informacemi o objektech uložených na haldě):
>>> from guppy import hpy >>> h=hpy() >>> h.doc Top level interface to Heapy. Available attributes: Anything Prod Via iso Clodo Rcs doc load Id Root findex monitor Idset Size heap pb Module Type heapu setref Nothing Unity idset test
a:
>>> help(hpy) Help on function hpy in module guppy: hpy(ht=None) Main entry point to the Heapy system. Returns an object that provides a session context and will import required modules on demand. Some commononly used methods are: .heap() get a view of the current reachable heap .iso(obj..) get information about specific objects The optional argument, useful for debugging heapy itself, is: ht an alternative hiding tag
16. Přístup k informacím o objektech uložených na haldě (heap)
Pravděpodobně zcela nejužitečnější funkcí nabízenou celým balíčkem guppy3 je metoda heap dostupná přes třídy z podbalíčku heapy (hpy). Tato metoda umožňuje získat informace o hodnotách (objektech) uložených na haldě a vracet tyto informace v podobě, která je snadno programově zpracovatelná. Nalezneme zde i různé další varianty této metody, například heapu atd.:
Help on method heap in module guppy.heapy.Use: heap() method of guppy.heapy.Use._GLUECLAMP_ instance heap() -> IdentitySet[1] Traverse the heap from a root to find all reachable and visible objects. The objects that belong to a heapy instance are normally not included. Return an IdentitySet with the objects found, which is presented as a table partitioned according to a default equivalence relation (Clodo [3]). See also: setref[2] References [0] heapy_Use.html#heapykinds.Use.heap [1] heapy_UniSet.html#heapykinds.IdentitySet [2] heapy_Use.html#heapykinds.Use.setref [3] heapy_Use.html#heapykinds.Use.Clodo heapu(rma=1, abs=0, stat=1) method of guppy.heapy.Use._GLUECLAMP_ instance heapu() -> Stat Finds the objects in the heap that remain after garbage collection but are _not_ reachable from the root. This can be used to find objects in extension modules that remain in memory even though they are gc-collectable and not reachable. Returns an object containing a statistical summary of the objects found - not the objects themselves. This is to avoid making the objects reachable. See also: setref[1] References [0] heapy_Use.html#heapykinds.Use.heapu [1] heapy_Use.html#heapykinds.Use.setref
Podívejme se nyní na některé způsoby použití metody heap. Získáme souhrnné informace o všech objektech na haldě a necháme si zobrazit jejich statistiku. K tomu nám postačují pouhé tři řádky kódu:
from guppy import hpy h=hpy() print(h.heap())
Výsledkem bude tabulka, která obsahuje jak informace o celkovém počtu objektů (120000 objektů pro interpretaci programu se třemi řádky!!!), tak i celkové obsazení haldy. To však není vše, protože objekty jsou rozděleny do skupin podle jejich typu (str, tuple atd.) a tabulka obsahuje jak celkový počet objektů daného typu, tak i obsazení paměti:
Partition of a set of 121759 objects. Total size = 12892713 bytes. Index Count % Size % Cumulative % Kind (class / dict of class) 0 31829 26 2973098 23 2973098 23 str 1 21959 18 2072008 16 5045106 39 tuple 2 6716 6 1186311 9 6231417 48 types.CodeType 3 1158 1 1052208 8 7283625 56 type 4 13621 11 1047842 8 8331467 65 bytes 5 6319 5 859384 7 9190851 71 function 6 25507 21 719732 6 9910583 77 int 7 1158 1 568640 4 10479223 81 dict of type 8 327 0 508584 4 10987807 85 dict of module 9 760 1 481272 4 11469079 89 dict (no owner) <270 more rows. Type e.g. '_.more' to view.>
Mimochodem: na starší verzi Pythonu (3.6.6) dostaneme zcela odlišné hodnoty (i proto si ji pro jeden projekt udržuji :-):
Partition of a set of 41461 objects. Total size = 4814075 bytes. Index Count % Size % Cumulative % Kind (class / dict of class) 0 11614 28 1041238 22 1041238 22 str 1 10110 24 733424 15 1774662 37 tuple 2 563 1 462216 10 2236878 46 type 3 2525 6 365032 8 2601910 54 types.CodeType 4 5024 12 346353 7 2948263 61 bytes 5 2455 6 333880 7 3282143 68 function 6 563 1 271736 6 3553879 74 dict of type 7 106 0 187896 4 3741775 78 dict of module 8 261 1 183416 4 3925191 82 dict (no owner) 9 402 1 148928 3 4074119 85 set <118 more rows. Type e.g. '_.more' to view.>
Pokusme se skript upravit tak, že v něm vytvoříme velký řetězec a poté opět zavoláme metodu heap:
from guppy import hpy x="?"*100000000 h=hpy() print(h.heap())
Nyní bude výsledek vypadat odlišně, přesně podle očekávání (viz první řádek tabulky s hodnotami typu str):
Partition of a set of 41464 objects. Total size = 104814244 bytes. Index Count % Size % Cumulative % Kind (class / dict of class) 0 11616 28 101041345 96 101041345 96 str 1 10110 24 733448 1 101774793 97 tuple 2 563 1 462216 0 102237009 98 type 3 2525 6 365032 0 102602041 98 types.CodeType 4 5024 12 346363 0 102948404 98 bytes 5 2455 6 333880 0 103282284 99 function 6 563 1 271736 0 103554020 99 dict of type 7 106 0 187896 0 103741916 99 dict of module 8 261 1 183416 0 103925332 99 dict (no owner) 9 402 1 148928 0 104074260 99 set <118 more rows. Type e.g. '_.more' to view.>
Ovšem metoda heap nezobrazí pouze tabulku, ale umožňuje nám (například interaktivně nebo i programově) objekty procházet a zkoumat je. Například si můžeme nechat vypsat statistiku o první řádku tabulky, tedy o všech objektem typu str (všechny další příklady jsou prováděny z REPLu Pythonu):
>>> h.heap()[0] Partition of a set of 34181 objects. Total size = 3177461 bytes. Index Count % Size % Cumulative % Kind (class / dict of class) 0 34181 100 3177461 100 3177461 100 str
Necháme si vypsat nejdelší řetězce (resp. jejich prvních několik znaků):
>>> h.heap()[0].byid Set of 34202 objects. Total size = 3179683 bytes. Index Size % Cumulative % Representation (limited) 0 7423 0.2 7423 0.2 'The class Bi... copy of S.\n' 1 6512 0.2 13935 0.4 '\nThe ``code...hentication``' 2 6327 0.2 20262 0.6 'Configuratio... by spaces.\n' 3 6150 0.2 26412 0.8 "Support for ... 'error'.\n\n" 4 5573 0.2 31985 1.0 'Controls for...ger`.\n\n ' 5 4791 0.2 36776 1.2 'Heap queues\...at Art! :-)\n' 6 4791 0.2 41567 1.3 'Heap queues\...at Art! :-)\n' 7 4708 0.1 46275 1.5 ' Retry confi...equest.\n ' 8 4252 0.1 50527 1.6 'Serialize ``...ible.\n\n ' 9 4114 0.1 54641 1.7 '\n Ge...ib`\n '
Nyní naalokujeme delší řetězec (10000 znaků) a zopakujeme předchozí operaci. Nový řetězec se dostane na samotný vrchol tabulky (a jeho velikost přesně odpovídá popisu, který jsme si uvedli v předchozích kapitolách – 49 bajtů + počet znaků pro ASCII řetězce):
>>> x="*"*10000 >>> h.heap()[0].byid Set of 34204 objects. Total size = 3189794 bytes. Index Size % Cumulative % Representation (limited) 0 10049 0.3 10049 0.3 '************...*************' 1 7423 0.2 17472 0.5 'The class Bi... copy of S.\n' 2 6512 0.2 23984 0.8 '\nThe ``code...hentication``' 3 6327 0.2 30311 1.0 'Configuratio... by spaces.\n' 4 6150 0.2 36461 1.1 "Support for ... 'error'.\n\n" 5 5573 0.2 42034 1.3 'Controls for...ger`.\n\n ' 6 4791 0.2 46825 1.5 'Heap queues\...at Art! :-)\n' 7 4791 0.2 51616 1.6 'Heap queues\...at Art! :-)\n' 8 4708 0.1 56324 1.8 ' Retry confi...equest.\n ' 9 4252 0.1 60576 1.9 'Serialize ``...ible.\n\n ' <34194 more rows. Type e.g. '_.more' to view.>
Řetězec necháme odstranit správcem paměti a opět se podíváme na výsledky:
>>> x=None >>> h.heap()[0].byid Set of 34203 objects. Total size = 3179745 bytes. Index Size % Cumulative % Representation (limited) 0 7423 0.2 7423 0.2 'The class Bi... copy of S.\n' 1 6512 0.2 13935 0.4 '\nThe ``code...hentication``' 2 6327 0.2 20262 0.6 'Configuratio... by spaces.\n' 3 6150 0.2 26412 0.8 "Support for ... 'error'.\n\n" 4 5573 0.2 31985 1.0 'Controls for...ger`.\n\n ' 5 4791 0.2 36776 1.2 'Heap queues\...at Art! :-)\n' 6 4791 0.2 41567 1.3 'Heap queues\...at Art! :-)\n' 7 4708 0.1 46275 1.5 ' Retry confi...equest.\n ' 8 4252 0.1 50527 1.6 'Serialize ``...ible.\n\n ' 9 4114 0.1 54641 1.7 '\n Ge...ib`\n ' <34193 more rows. Type e.g. '_.more' to view.>
17. Proč se tedy liší velikosti hodnot True a False?
Zbývá nám ještě odpovědět na otázku z titulku tohoto článku, tedy proč se velikosti hodnot True a False odlišují. Nejprve si musíme uvědomit, o jakých hodnotách se vlastně bavíme. Z historických důvodů totiž programovací jazyk Python reprezentuje hodnotu False jako nulu a hodnotu True jako jedničku. Nejedná se však o pouhou (řekněme) pseudoekvivalenci získanou na základě nějakých konverzních pravidel, ale o (pro naprostou většinu operací) skutečnou ekvivalenci:
>>> 0 == False True >>> 1 == True True >>> 2 == True False
Z tohoto pohledu jsou tedy hodnoty True a False instancemi třídy Number! Vyzkoušejme si to:
>>> import numbers >>> isinstance(0, numbers.Number) True >>> isinstance(1, numbers.Number) True >>> isinstance(True, numbers.Number) True >>> isinstance(False, numbers.Number) True
Nyní tedy víme, že nám bude postačovat zjistit, z jakého důvodu je celočíselná hodnota 0 uložena v operační paměti s jinou velikostí než celočíselná hodnota 1, protože naprosto stejná pravidla budou platit pro hodnotu False a hodnotu True.
18. Způsob uložení celočíselných hodnot Pythonu
V Pythonu 3 jsou celočíselné hodnoty reprezentovány datovým typem interně nazvaným long (a externě se jedná o objekty typu int). Ovšem nejedná se o stejný long, jaký známe například z programovacích jazyků C, C++ či Javy, protože typ long v podání Pythonu znamená, že uložená celočíselná hodnota může mít prakticky jakýkoli rozsah (teoreticky jsme omezeni jen kapacitou operační paměti). To mj. znamená, že nejsme omezeni pouze například na „klasický“ 32bitový či 64bitový rozsah, tedy na hodnoty –231..231-1 či –263..263-1 (popř. na rozsah 64bitový).
Způsob uložení hodnot typu long tedy musí být do značné míry adaptivní, což znamená, že malé hodnoty budou uloženy v kratším paměťovém bloku, než hodnoty větší (resp. přesněji řečeno hodnoty více vzdálené od nuly). To pochopitelně komplikuje a prodlužuje všechny výpočty, takže obecně platí, že v této oblasti bude Python vždy pomalejší, než nativní kód popř. než skripty napsané v interpretovaných jazycích, které podporují standardní formát celočíselných hodnot implementovaný přímo na mikroprocesoru.
Konkrétně vypadá paměťová struktura s celými čísly typu long následovně:
struct _longobject { PyObject_HEAD _PyLongValue long_value; };
Samotná hlavička objektu není nyní příliš zajímavá (bylo by to téma na samostatný článek), takže se podívejme na druhý prvek celé struktury, což je opět datová struktura:
typedef struct _PyLongValue { uintptr_t lv_tag; /* Number of digits, sign and flags */ digit ob_digit[1]; } _PyLongValue;
Tady se skrývají zajímavější informace. V prvním prvku struktury jsou uloženy informace o počtu cifer, znaménko a další příznaky. A druhý prvek je polem, do něhož jsou uloženy bajty, z nichž se celočíselná hodnota skládá. Výpočet uložené hodnoty lze zapsat takto:
value = SUM(for i=0 through abs(ob_size)-1) ob_digit[i] * 2**(SHIFT*i)
kde ob_size je jeden z atributů každé hodnoty a SHIFT může být buď 15 nebo 30. V prvním případě, kdy je SHIFT==15, se pracuje se 16bitovými hodnotami, ve druhém případě s hodnotami 32bitovými (tato volba je provedena při překladu interpretru Pythonu; poté ji již není možné měnit).
Speciálním případem je nula, která má ob_size nulový a tedy se nealokuje žádné pole ob_digit. Ale již pro jedničku je nutné pole alokovat, i když pochopitelně jen s jedním prvkem. A právě zde je tedy odpověď na původní otázku, proč je velikost False menší než velikost True. False je hodnota odpovídající celočíselné nule a tudíž ji lze v paměti uložit bez pole ob_digits. Naproti tomu True je již plnohodnotný celočíselný objekt, jehož velikost je stejná, jako velikost objektů/hodnot 1 až 230 (na současných platformách).
19. Repositář s demonstračními příklady
Zdrojové kódy všech prozatím popsaných demonstračních příkladů určených pro programovací jazyk Python 3 byly uloženy do Git repositáře dostupného na adrese https://github.com/tisnik/most-popular-python-libs. V případě, že nebudete chtít klonovat celý repositář (ten je ovšem stále velmi malý, dnes má velikost zhruba několik desítek kilobajtů), můžete namísto toho použít odkazy na jednotlivé příklady, které naleznete v následující tabulce:
20. Odkazy na Internetu
- Top 5 Python Memory Profilers
https://stackify.com/top-5-python-memory-profilers/ - Pympler na GitHubu
https://github.com/pympler/pympler - Pympler na PyPI
https://pypi.org/project/Pympler/ - Dokumentace k balíčku Pympler
https://pympler.readthedocs.io/en/latest/ - Guppy 3 na GitHubu
https://github.com/zhuyifei1999/guppy3/ - Guppy 3 na PyPI
https://pypi.org/project/guppy3/ - Memory Profiler na GitHubu
https://github.com/pythonprofilers/memory_profiler - Memory Profiler na PyPI
https://pypi.org/project/memory-profiler/ - How to use guppy/heapy for tracking down memory usage
https://smira.ru/wp-content/uploads/2011/08/heapy.html - Identifying memory leaks
https://pympler.readthedocs.io/en/latest/muppy.html#muppy - How do I determine the size of an object in Python?
https://stackoverflow.com/questions/449560/how-do-i-determine-the-size-of-an-object-in-python - Why is bool a subclass of int?
https://stackoverflow.com/questions/8169001/why-is-bool-a-subclass-of-int - Memory Management in Python
https://realpython.com/python-memory-management/ - Why do ints require three times as much memory in Python?
https://stackoverflow.com/questions/23016610/why-do-ints-require-three-times-as-much-memory-in-python - cpython/Include/cpython/longintrepr.h
https://github.com/python/cpython/blob/main/Include/cpython/longintrepr.h#L64 - sys — System-specific parameters and functions
https://docs.python.org/3/library/sys.html - Python 3.3 s flexibilní reprezentací řetězců
https://www.root.cz/clanky/interni-reprezentace-retezcu-v-ruznych-jazycich-od-pocitacoveho-praveku-po-soucasnost/#k17