Cesta ke kontrole typu 4 milionů řádků kódu Pythonu. Část 2

Dnes zveřejňujeme druhou část překladu materiálu o tom, jak Dropbox organizoval kontrolu typu pro několik milionů řádků kódu Pythonu.

Cesta ke kontrole typu 4 milionů řádků kódu Pythonu. Část 2

Přečtěte si část první

Oficiální podpora typu (PEP 484)

Naše první vážné experimenty s mypy jsme provedli na Dropboxu během Hack Week 2014. Hack Week je týdenní akce pořádaná Dropboxem. Během této doby mohou zaměstnanci pracovat na čem chtějí! Některé z nejslavnějších technologických projektů Dropboxu začaly na akcích, jako jsou tyto. V důsledku tohoto experimentu jsme dospěli k závěru, že mypy vypadá slibně, ačkoli projekt ještě není připraven pro široké použití.

V té době byla ve vzduchu myšlenka standardizovat systémy tipování typu Python. Jak jsem řekl, od Pythonu 3.0 bylo možné pro funkce používat typové anotace, ale byly to jen libovolné výrazy, bez definované syntaxe a sémantiky. Během provádění programu byly tyto anotace z větší části jednoduše ignorovány. Po Hack Week jsme začali pracovat na standardizaci sémantiky. Tato práce vedla ke vzniku PEP 484 (Na tomto dokumentu jsme spolupracovali Guido van Rossum, Łukasz Langa a já).

Na naše motivy se dalo pohlížet ze dvou stran. Nejprve jsme doufali, že celý ekosystém Pythonu by mohl přijmout společný přístup k používání tipů typu (termín používaný v Pythonu jako ekvivalent „typových anotací“). To by vzhledem k možným rizikům bylo lepší než použití mnoha vzájemně nekompatibilních přístupů. Za druhé, chtěli jsme otevřeně diskutovat o mechanismech typových anotací s mnoha členy komunity Pythonu. Tato touha byla částečně diktována tím, že bychom v očích širokých mas pythonských programátorů nechtěli vypadat jako „odpadlíci“ od základních myšlenek jazyka. Jedná se o dynamicky typovaný jazyk, známý jako „duck typing“. V komunitě na samém začátku nemohl nevzniknout poněkud podezřelý postoj k myšlence statického psaní. Ale tento sentiment nakonec opadl poté, co bylo jasné, že statické psaní nebude povinné (a poté, co si lidé uvědomili, že je to vlastně užitečné).

Syntaxe nápovědy typu, která byla nakonec přijata, byla velmi podobná té, kterou v té době podporoval mypy. PEP 484 byl vydán s Pythonem 3.5 v roce 2015. Python již nebyl dynamicky typovaným jazykem. Rád přemýšlím o této události jako o významném milníku v historii Pythonu.

Začátek migrace

Na konci roku 2015 vytvořil Dropbox tým tří lidí pro práci na mypy. Patřili k nim Guido van Rossum, Greg Price a David Fisher. Od této chvíle se situace začala vyvíjet extrémně rychle. První překážkou růstu mypy byl výkon. Jak jsem naznačil výše, v začátcích projektu jsem přemýšlel o překladu implementace mypy do C, ale tento nápad byl prozatím ze seznamu vyškrtnut. Zůstali jsme u spouštění systému pomocí interpretu CPython, který není dostatečně rychlý pro nástroje jako mypy. (Nepomohl nám ani projekt PyPy, alternativní implementace Pythonu s JIT kompilátorem.)

Naštěstí nám zde pomohla některá vylepšení algoritmů. Prvním výkonným „akcelerátorem“ byla implementace inkrementální kontroly. Myšlenka tohoto vylepšení byla jednoduchá: pokud se všechny závislosti modulu od předchozího spuštění mypy nezměnily, můžeme při práci se závislostmi použít data uložená během předchozího spuštění. Potřebovali jsme pouze provést kontrolu typu na upravených souborech a na souborech, které na nich závisely. Mypy šel ještě o něco dále: pokud se vnější rozhraní modulu nezměnilo, mypy předpokládal, že ostatní moduly, které tento modul importovaly, není třeba znovu kontrolovat.

Inkrementální kontrola nám hodně pomohla při anotování velkého množství existujícího kódu. Jde o to, že tento proces obvykle zahrnuje mnoho opakovaných běhů mypy, protože do kódu jsou postupně přidávány a postupně vylepšovány anotace. První spuštění mypy bylo stále velmi pomalé, protože bylo potřeba zkontrolovat spoustu závislostí. Poté jsme pro zlepšení situace implementovali mechanismus vzdáleného ukládání do mezipaměti. Pokud mypy zjistí, že místní mezipaměť je pravděpodobně zastaralá, stáhne aktuální snímek mezipaměti pro celou kódovou základnu z centralizovaného úložiště. Poté provede přírůstkovou kontrolu pomocí tohoto snímku. To nás posunulo o další velký krok ke zvýšení výkonu mypy.

Bylo to období rychlého a přirozeného přijetí kontroly typu v Dropboxu. Na konci roku 2016 jsme již měli přibližně 420000 XNUMX řádků kódu Pythonu s typovými anotacemi. Mnoho uživatelů bylo z kontroly typu nadšeno. Stále více vývojových týmů používalo Dropbox mypy.

Všechno tehdy vypadalo dobře, ale měli jsme ještě hodně práce. Začali jsme pravidelně provádět průzkumy mezi interními uživateli, abychom identifikovali problémové oblasti projektu a pochopili, jaké problémy je třeba nejprve vyřešit (tato praxe se ve společnosti používá dodnes). Nejdůležitější, jak se ukázalo, byly dva úkoly. Zaprvé jsme potřebovali více typového pokrytí kódu, zadruhé jsme potřebovali, aby mypy fungovalo rychleji. Bylo naprosto jasné, že naše práce na zrychlení mypy a implementaci do firemních projektů ještě zdaleka není dokončena. S plným vědomím důležitosti těchto dvou úkolů jsme se pustili do jejich řešení.

Více produktivity!

Přírůstkové kontroly zrychlily mypy, ale nástroj stále nebyl dostatečně rychlý. Mnoho postupných kontrol trvalo asi minutu. Důvodem byly cyklické dovozy. To asi nepřekvapí nikoho, kdo pracoval s velkými kódovými bázemi napsanými v Pythonu. Měli jsme sady stovek modulů, z nichž každý nepřímo importoval všechny ostatní. Pokud byl změněn jakýkoli soubor v importní smyčce, mypy musel zpracovat všechny soubory v této smyčce a často všechny moduly, které importovaly moduly z této smyčky. Jedním takovým cyklem byla nechvalně známá „spleť závislostí“, která způsobila v Dropboxu spoustu problémů. Jakmile tato struktura obsahovala několik stovek modulů, při importu, přímo či nepřímo, mnoha testech, byla použita i v produkčním kódu.

Zvažovali jsme možnost „rozmotat“ kruhové závislosti, ale neměli jsme na to prostředky. Bylo tam příliš mnoho kódu, který jsme neznali. V důsledku toho jsme přišli s alternativním přístupem. Rozhodli jsme se, že mypy bude fungovat rychle i v přítomnosti „spletenců závislostí“. Tohoto cíle jsme dosáhli pomocí démona mypy. Démon je serverový proces, který implementuje dvě zajímavé funkce. Za prvé ukládá informace o celé kódové základně do paměti. To znamená, že pokaždé, když spustíte mypy, nemusíte načítat data uložená v mezipaměti týkající se tisíců importovaných závislostí. Za druhé pečlivě na úrovni malých strukturních celků analyzuje závislosti mezi funkcemi a jinými entitami. Například pokud funkce foo volá funkci bar, pak vzniká závislost foo z bar. Když se soubor změní, démon nejprve izolovaně zpracuje pouze změněný soubor. Poté se podívá na externě viditelné změny tohoto souboru, jako jsou změněné podpisy funkcí. Démon používá podrobné informace o importech pouze k dvojité kontrole těch funkcí, které skutečně používají upravenou funkci. Obvykle s tímto přístupem musíte zkontrolovat velmi málo funkcí.

Implementace toho všeho nebyla snadná, protože původní implementace mypy byla silně zaměřena na zpracování jednoho souboru po druhém. Museli jsme řešit mnoho hraničních situací, jejichž vznik si vyžádal opakované kontroly v případech, kdy se v kódu něco změnilo. Například k tomu dojde, když je třídě přiřazena nová základní třída. Jakmile jsme udělali, co jsme chtěli, dokázali jsme zkrátit dobu provádění většiny přírůstkových kontrol na pouhých několik sekund. Zdálo se nám to jako velké vítězství.

Ještě vyšší produktivita!

Spolu se vzdáleným ukládáním do mezipaměti, o kterém jsem hovořil výše, démon mypy téměř úplně vyřešil problémy, které vznikají, když programátor často spouští kontrolu typu a provádí změny v malém počtu souborů. Výkon systému v nejméně příznivém případě použití však stále nebyl optimální. Čisté spuštění mypy může trvat více než 15 minut. A to bylo mnohem víc, než s čím bychom byli spokojeni. Každý týden se situace zhoršovala, protože programátoři pokračovali v psaní nového kódu a přidávání anotací ke stávajícímu kódu. Naši uživatelé byli stále hladoví po větším výkonu, ale rádi jsme jim vyšli vstříc.

Rozhodli jsme se vrátit k jedné z dřívějších myšlenek ohledně mypy. Konkrétně převést kód Pythonu do kódu C. Experimentování s Cythonem (systém, který umožňuje překládat kód napsaný v Pythonu do kódu C) nám nepřineslo žádné viditelné zrychlení, a tak jsme se rozhodli oživit myšlenku psaní vlastního kompilátoru. Vzhledem k tomu, že kódová báze mypy (napsaná v Pythonu) již obsahovala všechny potřebné typové anotace, řekli jsme si, že by stálo za to zkusit tyto anotace použít ke zrychlení systému. Rychle jsem vytvořil prototyp, abych tento nápad otestoval. U různých mikrobenchmarků vykázal více než 10násobný nárůst výkonu. Naším nápadem bylo zkompilovat moduly Pythonu do modulů C pomocí Cythonu a převést typové anotace na run-time typové kontroly (typové anotace jsou obvykle za běhu ignorovány a používány pouze systémy pro kontrolu typu). Vlastně jsme plánovali přeložit implementaci mypy z Pythonu do jazyka, který byl navržen pro statické typování, který by vypadal (a z větší části i fungoval) přesně jako Python. (Tento druh migrace mezi jazyky se stal jakousi tradicí projektu mypy. Původní implementace mypy byla napsána v Alore, poté existoval syntaktický hybrid Javy a Pythonu).

Zaměření na rozšiřující API CPython bylo klíčem k tomu, abychom neztratili možnosti řízení projektů. Nepotřebovali jsme implementovat virtuální stroj ani žádné knihovny, které mypy potřeboval. Navíc bychom stále měli přístup k celému ekosystému Pythonu a všem nástrojům (jako je pytest). To znamenalo, že jsme mohli pokračovat v používání interpretovaného kódu Pythonu během vývoje, což nám umožnilo pokračovat v práci s velmi rychlým vzorem provádění změn kódu a jeho testování, spíše než čekat na kompilaci kódu. Vypadalo to, že jsme seděli takříkajíc na dvou židlích skvěle, a líbilo se nám to.

Kompilátor, který jsme nazvali mypyc (protože používá mypy jako front-end pro analýzu typů), se ukázal jako velmi úspěšný projekt. Celkově jsme dosáhli přibližně 4násobného zrychlení pro časté běhy mypy bez ukládání do mezipaměti. Vývoj jádra projektu mypyc zabral malému týmu Michaela Sullivana, Ivana Levkivského, Hugha Hahna a mně asi 4 kalendářní měsíce. Toto množství práce bylo mnohem menší než to, co by bylo potřeba k přepsání mypy, například v C++ nebo Go. A museli jsme v projektu udělat mnohem méně změn, než bychom museli udělat při jeho přepisování do jiného jazyka. Také jsme doufali, že se nám podaří přivést mypyc na takovou úroveň, aby jej ostatní programátoři Dropboxu mohli použít ke kompilaci a zrychlení svého kódu.

Abychom dosáhli této úrovně výkonu, museli jsme použít některá zajímavá technická řešení. Kompilátor tedy může urychlit mnoho operací pomocí rychlých, nízkoúrovňových konstrukcí C. Například zkompilované volání funkce je převedeno na volání funkce C. A takové volání je mnohem rychlejší než volání interpretované funkce. Některé operace, jako je vyhledávání ve slovníku, stále zahrnovaly používání běžných volání C-API z CPythonu, která byla při kompilaci jen nepatrně rychlejší. Podařilo se nám eliminovat dodatečné zatížení systému vytvořené interpretací, ale to v tomto případě přineslo jen malý zisk z hlediska výkonu.

Abychom identifikovali nejběžnější „pomalé“ operace, provedli jsme profilování kódu. Vyzbrojeni těmito daty jsme se pokusili buď vyladit mypyc tak, aby generoval rychlejší kód C pro takové operace, nebo přepsat odpovídající kód Pythonu pomocí rychlejších operací (a někdy jsme prostě neměli dostatečně jednoduché řešení pro tento nebo jiný problém) . Přepsání kódu Pythonu bylo často snazším řešením problému, než kdyby kompilátor automaticky provedl stejnou transformaci. Z dlouhodobého hlediska jsme chtěli mnoho z těchto transformací automatizovat, ale v té době jsme se soustředili na urychlení mypy s minimálním úsilím. A při směřování k tomuto cíli jsme uřízli několik rohů.

Chcete-li se pokračovat ...

Vážení čtenáři! Jaké byly vaše dojmy z projektu mypy, když jste se dozvěděli o jeho existenci?

Cesta ke kontrole typu 4 milionů řádků kódu Pythonu. Část 2
Cesta ke kontrole typu 4 milionů řádků kódu Pythonu. Část 2

Zdroj: www.habr.com

Přidat komentář