PythonJiří ZnamenáčekPráce s velkými soubory pomocí modulu „mmap“2019-10-24
Pokud potřebujete pracovat s opravdu velikými daty (například v souboru), s výhodou můžete využít modulu mmap, který dokáže nad daty poskytnout přístup tvářící se zároveň jako soubor a zároveň jako bytearray() tím, že data (souboru) namapuje do paměti k přímému přístupu.
Podmínkou ovšem je, že se vám uvedený počet bajtů podaří vecpat do paměti jako kontinuální úsek, a pak samozřejmě také, že váš operační systém zvládne s tak velikým úsekem vůbec operovat.
Podle zkušeností z netu leží hranice použitelnosti pro 32-bitové operační systémy někde mezi jedním a dvěma GB. Pro systémy 64-bitové se ovšem můžete rozohnit dostatečně (pokud máte dost RAMky).
Paměť pro práci můžete zpřístupnit dvěma způsoby:
jako anonymní blok paměti
>>> import mmap
>>> m = mmap.mmap(-1, 1000)
>>> m
<mmap.mmap object at 0x000001BAF0463930>
z existujícího souboru (zde čistě pro čtení)
import mmap
with open(PATH, 'r') as f:
with mmap.mmap(f.fileno(), SIZE, access=mmap.ACCESS_READ) as m:
...
Pokud je SIZE 0, namapujete celý subor, jinak pouze jeho příslušnou část.
PS: Konstruktor mmap.mmap() má jiný tvar na Windows a na Linuxu, nicméně základní použití je stejné. Především má jako poslední nepovinný parametr offset (výchozí hodnota 0), v němž můžete pomocí násobků mmap.ALLOCATIONGRANULARITY* určit místo, odkud mapovaný soubor bude v paměti k dispozici.
* U mě v době psaní přednášky 65536.
Prakticky to znamená, že z velkých souborů můžete v paměti zpřístupnit pouze jejich potřebnou část mezi dvěma adresami vzdálenými od sebe o násobek uvedené velikosti.
Jak je vidět, podporuje do paměti namapovaný objekt jistý mix slibovaných obojakých činností souboru a bajtového pole, byť tedy část svými vlastními metodami:
Do memory mapped objektu tedy můžeme především zapisovat bajty (pomocí write()) a také je z něj naopak číst (read()), můžeme v něm hledat (binární) podřetězce (find() a rfind()), ale také načítat data (textový) řádek po řádku (readline()).
Dále pak máme k dispozici tyto nové metody:
read_byte() – vrátí bajt (jako celé číslo) z aktuální pozice (a posune ukazatel v proudu o 1);
write_byte(BYTE) – zapíše na aktuální pozici zadaný (jako celé číslo) bajt (a posune ukazatel v proudu o 1);
Samozřejmě za předpokladu, že mmap byl otevřen pro zápis. Při ACCESS_READ dostaneme výjimku TypeError.
move(CÍL, ZDROJ, POČET) – zkopíruje POČET bajtů od adresy ZDROJ na adresu CÍL;
Samozřejmě mmap stejně jako v předchozím případě nesmí být otevřen jako ACCESS_READ.
resize(NováVelikost) – změní velikost namapované paměti (a případně i odpovídajícího souboru, byl-li mmap vytvořen z něj) na velikost NováVelikost;
Nepřekvapivě opět selže s TypeError při zavedení mmapu jako ACCESS_READ nebo ACCESS_COPY.
madvise(option[, start[, length]]) – na systémech, které podporují systémové volání madvise(), přepošle mu uvedený požadavek (např. mmap.MADV_DONTDUMP).
Ne všechny požadavky jsou přístupné na všech operačních systémech.
Jelikož memory mapped objekt obchází klasické přístupové cesty k proudu a vyhýbá se dodatečnému kopírování dat, můžeme právem očekávat, že bude rychlejší než přístup přes klasické souborové API.
Pokusné čtení takřka pětigigového souboru postupně po čtyřech řádcích tento předpoklad potvrzuje, neboť přístup přes mmap..
import mmap
with open('ex3.fastq', 'r') as f:
with mmap.mmap(f.fileno(), 0, access=mmap.ACCESS_READ) as m:
while True:
header, seq, com, qual = m.readline(), m.readline(), m.readline(), m.readline()
if header == b'': break
..vykazoval čas kolem 24 sekund*, zatímco přístup přes souborové API..
with open('ex3.fastq', 'r') as f:
while True:
header, seq, com, qual = f.readline(), f.readline(), f.readline(), f.readline()
if header == '': break
..se dostal nejlépe na 41 sekund.
* Nutno podotknout, že při testování dosáhl první pokus času pouze 35 sekund, až všechny následující těch 24. Operační systém asi neměl ihned k dispozici takový kus paměti, zatímco po prvním pokusu už ano. Nicméně i v tomto případě bylo provedení programu rychlejší.
Mapování objektů do paměti se používá především pro zrychlení práce s velkými daty.
Naprosto typické použití je vyhledávání v obrovských datech pomocí regexpů. Jen nesmíte zapomenout, že memory mapped objekt jsou bajty, takže vzory pro regexpy musíte tvořit jako rb''.
Také není až takový problém (obzvláště u externích knihoven) natrefit na metody načítání dat, které beznadějně selžou pro velké vstupy a mmap pak bývá jediný způsob, jak uvedená data do programu dostat.