<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/xml" href="/cjs/screen.xsl" media="screen"?>
<lecture>

<meta>
  <maintitle>Python</maintitle>
  <author>Jiří Znamenáček</author>
  <title>Práce s velkými soubory pomocí modulu „mmap“</title>
  <date>2019-10-24</date>
  <link><!--a href="http://vyuka.ookami.cz" rel="external">http://vyuka.ookami.cz</a--></link>
</meta>
<!--
  „“–
  ↵ aneb &#x21B5; aneb \r aneb CR aneb CarriageReturn
-->


<slide title="Úvod">

    <p>
        Pokud potřebujete pracovat s opravdu velikými daty (například v souboru), s výhodou můžete využít modulu <em>mmap</em>, který dokáže nad daty poskytnout přístup tvářící se <strong>zároveň jako soubor a zároveň jako <em>bytearray()</em></strong> tím, že data (souboru) namapuje do paměti k přímému přístupu.
    </p>
    <p>
        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.
    </p>
    <note>
        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).
    </note>

</slide>
<slide title="Založení – „mmap()“">

    <p>
        Paměť pro práci můžete zpřístupnit dvěma způsoby:
    </p>
    <ul>
        <li>
            jako anonymní blok paměti (parametr <em>-1</em>) o velikosti 1000 bajtů
            <example lang="python">
>>> import mmap
>>> m = mmap.mmap(-1, 1000)
>>> m
&lt;mmap.mmap object at 0x000001BAF0463930>
            </example>
        </li>
        <li>
            z existujícího souboru (zde čistě pro čtení)
            <example lang="python">
import mmap
with open(PATH, 'r') as f:
    with mmap.mmap(f.fileno(), SIZE, access=mmap.ACCESS_READ) as m: 
        ...
            </example>
            <note>
                Pokud je <em>SIZE</em> 0, namapujete celý subor, jinak pouze jeho příslušnou část.
            </note>
        </li>
    </ul>
    <p>
        Konstruktor <a href="https://docs.python.org/3/library/mmap.html#mmap.mmap" class="external"><em>mmap.mmap()</em></a> má sice jiný tvar na Windows a na Linuxu, nicméně základní použití je stejné. Především má jako poslední nepovinný parametr <em>offset</em> (výchozí hodnota 0), v němž můžete pomocí násobků <em>mmap.ALLOCATIONGRANULARITY</em> <sup>*</sup> určit místo uvnitř souboru namapovaného do paměti, odkud bude jeho obsah k dispozici.
    </p>
    <notes>
        <note>
            <sup>*</sup> U mě v době psaní přednášky <code>65536</code>.
        </note>
        <note>
            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.
        </note>
    </notes>

</slide>
<slide title="Vlastnosti I">

    <p>
        Jak je vidět, podporuje do paměti namapovaný objekt jistý mix slibovaných obojakých činností <em>souboru</em> a <em>bajtového pole</em>, byť tedy část svými vlastními metodami:
    </p>
    <example lang="python">
>>> import mmap
>>> m = mmap.mmap(-1, 1000)
>>> m
&lt;mmap.mmap object at 0x000001BAF0463930>
>>> dir(m)
['__class__', '__delattr__', '__delitem__', '__dir__', '__doc__', '__enter__',
 '__eq__', '__exit__', '__format__', '__ge__', '__getattribute__', '__getitem__',
 '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__len__',
 '__lt__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__',
 '__setattr__', '__setitem__', '__sizeof__', '__str__', '__subclasshook__',
 'close', 'closed', 'find', 'flush', 'move', 'read', 'read_byte', 'readline',
 'resize', 'rfind', 'seek', 'size', 'tell', 'write', 'write_byte']
    </example>
    <p>
        Do <em>memory mapped</em> objektu tedy můžeme především zapisovat bajty (pomocí <em>write()</em>) a také je z něj naopak číst (<em>read()</em>), můžeme v něm hledat (binární) podřetězce (<em>find()</em> a <em>rfind()</em>), ale také načítat data (textový) řádek po řádku (<em>readline()</em>).
    </p>

</slide>
<slide title="Vlastnosti II">

    <p>
        Zajímavou možností je přístup k bajtům souboru pomocí výřezové notace:
    </p>
    <example lang="python" src="_files/mmap_1.py" />
    <note>
        PS: Výchozí nastavení <em>access=mmap.ACCESS_DEFAULT</em> vyžaduje otevření souboru alespoň v módu <code>br+</code>.
    </note>
    <p>
        Jen si při zápisu musíte hlídat, že se pohybujete v přiděleném rozmezí:
    </p>
    <example layout="vertical">
        <program src="_files/mmap_3.py" lang="python"/>
        <out src="_files/mmap_3.out" lang="text"/>
    </example>

</slide>
<slide title="Vlastnosti III">

    <p>
        Dále pak máme k dispozici tyto nové metody:
    </p>
    <ul>
        <li>
            <strong>read_byte()</strong> – vrátí bajt (jako celé číslo) z aktuální pozice (a posune ukazatel v proudu o 1);
        </li>
        <li>
            <strong>write_byte(BYTE)</strong> – zapíše na aktuální pozici zadaný (jako celé číslo) bajt (a posune ukazatel v proudu o 1);
            <note>
                Samozřejmě za předpokladu, že <em>mmap</em> byl otevřen pro zápis. Při <em>ACCESS_READ</em> dostaneme výjimku <em>TypeError</em>.
            </note>
        </li>
        <li>
            <strong>move(CÍL, ZDROJ, POČET)</strong> – zkopíruje <em>POČET</em> bajtů od adresy <em>ZDROJ</em> na adresu <em>CÍL</em>;
            <note>
                Samozřejmě <em>mmap</em> stejně jako v předchozím případě nesmí být otevřen jako <em>ACCESS_READ</em>.
            </note>
        </li>
        <li>
            <strong>resize(NováVelikost)</strong> – změní velikost namapované paměti (a případně i odpovídajícího souboru, byl-li <em>mmap</em> vytvořen z něj) na velikost <em>NováVelikost</em>;
            <note>
                Nepřekvapivě opět selže s <em>TypeError</em> při zavedení <em>mmap</em>u jako <em>ACCESS_READ</em> nebo <em>ACCESS_COPY</em>.
            </note>
        </li>
        <li>
            <strong>madvise(option[, start[, length]]) </strong> – na systémech, které podporují systémové volání <em>madvise()</em>, přepošle mu uvedený požadavek (např. <em>mmap.MADV_DONTDUMP</em>).
            <note>
                Ne všechny požadavky jsou přístupné na všech operačních systémech.
            </note>
        </li>
    </ul>

</slide>
<slide title="Srovnání rychlosti">

    <p>
        Jelikož <em>memory mapped</em> 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.
    </p>
    <p>
        Pokusné čtení takřka pětigigového souboru postupně po čtyřech řádcích tento předpoklad potvrzuje, neboť přístup přes <em>mmap</em>..
    </p>
    <example lang="python">
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
    </example>
    <p>
        ..vykazoval čas kolem 24 sekund<sup>*</sup>, zatímco přístup přes souborové API..
    </p>
    <example lang="python">
with open('ex3.fastq', 'r') as f:
    while True:
        header, seq, com, qual = f.readline(), f.readline(), f.readline(), f.readline()
        if header == '': break 
    </example>
    <p>
        ..se dostal nejlépe na 41 sekund.
    </p>
    <note>
        <sup>*</sup> 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ší.
    </note>

</slide>
<slide title="Použití">

    <p>
        Mapování objektů do paměti se používá především pro <strong>zrychlení práce s velkými daty</strong>.
    </p>
    <p>
        Naprosto typické použití je <strong>vyhledávání v obrovských datech pomocí regexpů</strong>. Jen nesmíte zapomenout, že <em>memory mapped</em> objekt jsou bajty, takže vzory pro regexpy musíte tvořit jako <code>rb''</code>.
    </p>
    <p>
        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 <em>mmap</em> pak bývá jediný způsob, jak uvedená data do programu dostat.
    </p>

</slide>


</lecture>