Zbytečné optimalizace

cs v kategorii code • 7 min. čtení
Mind the age! Most likely, its content is outdated. Especially if it’s technical.

Vývojáři mají skvělý dar. Optimalizovat to, co je nejvíc zbytečné. Aneb příklad z praxe. :-)

Potřebovali jsme funkci batches s následujícím chováním podporující jakýkoliv iterable:

>>> list(batches.batches(range(10), 5))
[[0, 1, 2, 3, 4], [5, 6, 7, 8, 9]]
>>> list(batches.batches(range(10), 3))
[[0, 1, 2], [3, 4, 5], [6, 7, 8], [9]]

Udělali jsem tedy základní implementaci během minutky:

def batches(iterable, batch_size):
    bucket = []
    for item in iterable:
        bucket.append(item)
        if len(bucket) == batch_size:
            yield bucket
            bucket = []
    if bucket:
        yield bucket

Jenže… jenže to nevypadá sexy. Přeci to musí jít udělat nějak cool! A taky efektivněji. Například pro velký batch_size to musí být náročné. Pojďme zkusit udělat generátor generátorů.

def batches(iterable, batch_size):
    def batch_generator():
        for x in range(batch_size):
            yield next(iterable)
     while iterable:
        yield batch_generator()

Vypadá to docela hezky. Jenže to má zásadní problém. Předchozí ukázka totiž nikdy neskončí. Jakmile se dojede vstupní iterable, funkce bude generovat prázdné generátory do nekonečna. To je potřeba nějak vyřešit…

První nápad je odchytávat StopIteration. Co ale dál? Během chvilky jsme se dostali do velmi nehezkých konstrukcí a stejně zůstali v nekonečné smyčce. Kolegu napadlo si z vnitřního iterátoru předávat, zda bude něco následovat nebo ne. Idea je taková, že si první elementy ohandluju ručně nějak takto:

def batches(iterable, batch_size):
    def batch_generator():
        try:
            first = next(iterable)
        except StopIteration:
            yield False  # Hele, uz nic neprijde, tak to stopni.
        else:
            yield True  # Jasne, jeste neco prijde.
            yield first
         for x in range(1, batch_size):
            yield next(iterable)
     while True:
        gen = batch_generator()
        if next(gen):  # Prvni polozka je rucne vlozena.
            yield gen
        else:
            break

Super! Máme generátory. Generují dokonce správně. Jenže… jenže to vypadá opravdu ošklivě. Navíc je podporován jen takový vstup, který zvládne next. Zkusili jsme testy, zda jdeme dobrým směrem a… horší než jednoduchá jasná implementace! Možná to šetří pamětí, ale podstatně trpí výkon kvůli samému generování.

Tak znovu na začátek a jinak a lépe. itertools obsahují funkci groupby. S pomocí Google jsme došli k následujícímu řešni:

def batches(iterable, batch_size):
    iterable = iter(iterable)
    counter = itertools.count()
     def func(key):
        return next(counter) // batch_size
     for key, group in itertools.groupby(iterable, func):
        yield group

Vypadá elegantně, ale pokud k tomu nebude výklad třikrát tak dlouhý, jako celá funkce, nebude jasné, co se děje. A výsledek? Taky pomalejší. Výkon s groupby se od předchozího kódu moc neliší.

Závěr: neoptimalizujte. Optimalizujte až v případě, kdy je to opravdu potřeba. Raději pište čitelný kód. A pokud už bude potřeba optimalizace, ten čitelný kód nemažte – nechte ho vedle toho optimalizovaného. Aspoň budete moci rychle přečíst, co daná funkce dělá, a především zkontrolovat, zda ta zoptimalizovaná dělá skutečně to, co má.








Může se vám také líbit

en Makefile with Python, November 6, 2017
en Fast JSON Schema for Python, October 1, 2018
en Deployment of Python Apps, August 15, 2018
cs Jasně, umím Git…, August 6, 2014
cs Checklist na zabezpečení webových aplikací, March 1, 2016

Další články z kategorie code.
Nenechte si ujít nové články díky Atom/RSS kanálu.



Poslední příspěvky

cs O klimatizaci, November 10, 2024 in family
cs První slůvka, November 3, 2024 in family
cs Jakou knihu čteš?, October 12, 2024 in family
cs V kolik chodíte spát?, September 29, 2024 in family
cs Neposedné miminko, September 8, 2024 in family