Hlavní navigace

Distcc: kompilujte distribuovaně a rychle

24. 1. 2013
Doba čtení: 8 minut

Sdílet

Sestavování binárních souborů ze zdrojových kódů může být někdy zdlouhavé. Dnes si představíme nástroj, pomocí kterého lze kompilaci paralelně zpracovávat na více počítačích a tím ušetřit spoustu času. To se může hodit nejen vývojářům, ale i uživatelům distribucí, které nepoužívají binární balíčky.

Překlad softwaru ze zdrojových kódů je standardním způsobem distribuce svobodného sofwaru. Některé linuxové distribuce, jako Gentoo, nebo Linux from Scratch, používají překlad ze zdrojových textů jako základní způsob instalace nových programů. Problémem je, že takový překlad, neboli kompilace, je vcelku náročný na procesorový výkon a tedy i čas. K jeho šetření je možné použít několik postupů, které se mohou vzájemně doplňovat:

  • Použití utility Make. Ta dokáže výrazně zrychlit opakované překlady při ladění chyb, neboť překládá znovu pouze ty zdrojové soubory, které byly změněny, což zjistí podle data poslední modifikace zdrojového a přeloženého binárního souboru.
  • Paralelizace sestavování. Spustí-li se příkaz make s parametrem -j, budou se překlady vzájemně nezávislých souborů spouštět paralelně. Tak je možno využít víc procesorových jader, nebo i samostatných serverů v clusteru.
  • Překlad v clusteru. Část zdrojového textu je možné odeslat sítí na jiný stroj, který provede vlastní překlad a vrátí binární kód. Přesně toto dělá distcc.

Jak funguje distcc

Ukažme si, jak probíhá sestavení jednoduchého programu v jazyce C:

  1. Nejprve je každý soubor zpracován preprocesorem. Jeho výstupem je opět text, tentokrát již ale zbavený direktiv preprocesoru (jako #include, nebo #ifdef) Také jsou do něj vloženy všechny hlavičkové soubory, čímž výrazně přibere na objemu, ale díky tomu je výsledný text zkompilovatelný bez potřeby jakýchkoli dalších souborů.
  2. Na předzpracovaný text je spuštěn vlastní kompilátor. Ten jej přeloží do tzv. objektového souboru s příponou .o. Ke každému zdrojovému souboru vznikne jeden objektový. Toto je obvykle ta časově nejnáročnější část.
  3. Vlastní sestavení spustitelného binárního souboru či knihovny provede linker. Ten vezme jeden nebo několik objektových souborů, složí je dohromady a přidá potřebné knihovní funkce, buď staticky, nebo jako odkazy na dynamické knihovny, které se načtou za běhu programu. Sestavení není až tak náročné na procesorový čas, spíše potřebuje velké množství paměti.

Program distcc v základním režimu urychluje pouze druhý jmenovaný bod kompilace. Dělá to velice jednoduše tak, že předzpracovaný text odešle sítí na kompilační server, a nazpět obdrží objektový soubor. Kompilačních serverů může mít v kompilaci nastaveno víc, pak dokáže pro každý zdrojový soubor vybrat jiný a odbavit je paralelně, to však jen v případě, že je sestavení paralelně spuštěno (například právě pomocí příkazu  make -j.)

Jednoduchá instalace

Chcete-li začít používat distcc, ujistěte se, že všechny servery i kompilující klient používají stejnou nebo velmi blízkou verzi GCC a mají stejnou architekturu. Pak stačí nainstalovat na všechny stroje balík distcc, nebo ho přímo zkompilovat ze zdrojových kódů.

Na kompilačních serverech pak existují dvě možnosti, jak k nim umožnit přístup. Ta tradiční spočívá ve spuštění serveru distccd. Ten standardně poslouchá na portu 3632/tcp a nepoužívá žádnou autentizaci. Autoři také varují, že serverový kód nemusí být neprůstřelný a jeho otevření do internetu by mohlo představovat riziko spuštění libovolného kódu na kompilačním serveru. Z toho důvodu je povinné omezit povolené zdrojové adresy přepínačem --allow. Alternativně je možné spouštět distccd ze superserveru inetd.

Jednodušší variantou je přistupovat ke kompilačnímu serveru protokolem SSH. V takovém případě stačí zajistit k serveru SSH přístup s uživatelskými oprávněními a pravděpodobně nastavit přihlašování pomocí klíčů. Přístup pomocí SSH má nevýhodu, že část výkonu CPU serveru i klienta je obětována šifrování spojení. Při výkonu současných procesorů to ale nepředstavuje zásadní rozdíl. Je také možné omezit využití daného klíče pouze na spuštění kompilačního serveru; je však třeba myslet na výše uvedené varování, že distcc nemusí být neprůstřelný. Vnucení spuštění distccd lze dosáhnout například připsáním následující konfigurace před vlastní ssh klíč v souboru  .ssh/authorized_keys:

command="DISTCC_TCP_CORK=0 distccd --inetd",no-agent-forwarding,no-port-forwarding,no-pty,no-user-rc,no-X11-forwarding ssh-rsa …

Nastavení klienta

Na počítači, který bude kompilaci řídit, je nastavení o něco složitější. Především je třeba určit seznam serverů, které budou ke kompilaci použity. Ten se může nacházet v proměnné prostředí DISTCC_HOSTS, nebo v konfiguračním souboru hosts, který se může nacházet v domácím adresáři uživatele ( ~/.distcc/hosts) nebo v systémové cestě (obvykle /etc/distcc/hosts). Jde o seznam adres oddělený mezerami. Uvedeme-li za adresu parametr ,lzo bude spojení se serverem komprimováno, což ušetří síť na úkor procesorů. Zvláštní význam má adresa localhost, která je interně interpretována jako kompilace na lokálním počítači, takže není navazováno zbytečně síťové spojení. Další zvláštní adresa --randomize způsobí náhodný výběr adres, jinak jsou preferovány adresy na začátku seznamu.

Pro připojení ke kompilačnímu serveru pomocí SSH je třeba před adresu připsat znak @, který může předcházet ještě uživatelské jméno. Je-li potřeba další nastavení, jako číslo portu nebo SSH klíč, je třeba jej provést v konfiguračním souboru SSH klienta. Víc než kdy jindy se zde hodí využít sdílené SSH spojení, jinak bude kompilace každého jednotlivého souboru zdržována navazováním TCP spojení, výměnou klíčů a autentizací klienta a serveru. Nastavení v souboru ~/.ssh/config může vypadat třeba takto:

host distccnode1
        Hostname distccnode1.example.net
        User distccuser
        Identityfile ~/.ssh/id_dsa-distcc
        Compression no
        BatchMode yes
        ControlMaster auto
        ControlPath ~/.ssh/controlsock-%h-%p-%r
        ControlPersist 10

V tomto případě první pokus o spojení k danému serveru založí šíťové spojení a řídicí soket, prostřednictvím kterého se připojí další klienti. Soket přežije ještě 10 sekund po ukončení posledního klienta, pak je spojení zrušeno. Protože jsou SSH klienti spouštěni v rychlém sledu, může při prvním spojení vzniknout několik řídicích spojení, které si vzájemně přepíšou soket. Později spuštění klienti ale již používají soket a ostatní marná řídicí spojení se časem zruší. Možností jak tento souběh eliminovat, je navázat řídicí spojení ručně, například z terminálu a vnutit distcc spojení pouze slave režim:

host distccnode1
        ControlMaster no
        ControlPath /path/to/controlsock

Maškaráda a další možnosti distribuce překladu

Máme-li nastaveno spojení ke vzdáleným serverům, zbývá vyřešit, jak donutit stávající kompilační skripty používat namísto překladače klienta distcc. V případě distribuce Gentoo toto není třeba příliš řešit, stačí přidat do souboru make.conf volbu FEATURES="distcc" a portage se o zbytek postará samo. Další možností je předání proměnné přímo příkazu make, například takto:

$ make -j8 CC=distcc

To však nefunguje se všemi kompilačními skripty. Proto distcc podporuje tzv. maškarádu. Ta funguje tak, že do adresáře, který se později ocitne v proměnné PATH na prvním místě umístíme symbolické odkazy pod názvy jednotlivých kompilátorů, které všechny míří na distcc klienta. Ten se při spuštění podívá, jak byl zavolán a spustí podle toho na serveru patřičný kompilátor. Spouští-li se kompilátor lokálně, distcc před jeho zavoláním odstraní z cesty adresář s maškarádovanými kompilátory, aby nedošlo k zacyklení. Jednoznačnou výhodou maškarády je, že není potřeba nijak zasahovat do kompilačních skriptů.

Když se liší architektury

Občas je vhodné používat jako kompilační server počítač s jinou architekturou, než řídicí stroj. Můžeme třeba na novém počítači s architekturou amd64 pomáhat s kompilací nevýkonnému počítači, třeba s architekturou x86, nebo ARM. Problémy jsou v takovém případě dva. Předně je potřeba nainstalovat na kompilační server patřičný cross-compiler, schopný generovat binární spustitelné soubory pro odlišnou architekturu. Postupy se různí podle distribuce, například Gentoo používá nástroj crossdev.

Již méně zřejmé je, že je potřeba provést nepatrnou, ale zásadní úpravu na straně klienta. Distcc totiž nezkoumá, zda se architektury serveru a klienta liší, takže pokud klient přikáže zavolat například příkaz gcc, na serveru se vykoná taktéž gcc a klient dostane nazpět binární soubor nativní pro server. Když ale klient explicitně specifikuje, že požaduje kompilátor pro svoji architekturu, například i686-pc-linux-gnu-gcc, přenese se tento příkaz na server a pakliže server daným kompilátorem disponuje (tedy má nainstalován příslušný cross-compiler), vrátí nazpět správný binární soubor. Je tedy potřeba v maškarádovém adresáři na klientovi pomocí wrapperu opravit „krátké“ názvy kompilátorů na dlouhé: (postup pochází z Gentoo DistCC Cross-compiling Guide)

# cd /usr/lib/distcc/bin/
# cat  << EOF > i686-pc-linux-gnu-wrapper
#!/bin/bash
exec /usr/lib/distcc/bin/i686-pc-linux-gnu-g${0:$[-2]} "$@"
EOF
# chmod +x i686-pc-linux-gnu-wrapper
# for name in c++ g++ gcc cc; do rm $name; ln -s i686-pc-linux-gnu-wrapper $name; done

Ještě větší zrychlení s pumpou

Od verze 3.0 podporuje distcc režim, který by měl umožnit ještě více kompilaci zrychlit tím, že i preprocesor bude spouštěn na serveru. Funguje to tak, že na klientovi běží skript v pythonu, který zdrojové soubory analyzuje, nachází hlavičkové soubory, které by server mohl potřebovat a ty mu posílá. Server soubory umisťuje do dočasného adresářového stromu kde následně spustí kompilátor. Protože u velkých softwarových projektů je tentýž soubor vkládán obvykle několiksetkrát, sníží tento režim množství přenesených dat, stejně jako zatížení klienta a kompilaci tak může zrychlit až třikrát. Při nějakém nestandardním způsobu používání preprocesoru se ale taková kompilace nemusí podařit, proto v případě selhání vzdálené kompilace distcc automaticky zopakuje pokus o kompilaci na klientovi.

Aby bylo tento režim možné použít, je potřeba na seznam serverů připsat volbu ,lzo,cpp za každé jméno serveru, který má být použit pro vzdálené volání preprocesoru. Dále je potřeba spustit na klientovi pomocí wrapperu pump program, starající se o analýzu zdrojových kódů, například takto:

$ pump make -j8 CC=distcc

Kontrola průběhu kompilace

Součástí balíku distcc jsou také dva jednoduché programy, sloužící ke sledování průběhu distribuované kompilace, textový distccmon-text a grafický  distccmon-gui.

Oba klienti čtou informace z pracovního adresáře distcc klienta, který je standardně umístěn v adresáři ~/.distcc/. Cestu je možno změnit nastavením proměnné prostředí DISTCC_DIR. Například průběh kompilace řízené systémem portage distribuce Gentoo je možné sledovat příkazem:

CS24_early

$ DISTCC_DIR=/var/tmp/portage/.distcc/ distccmon-gui &

Závěrem

Distcc používám s přestávkami přibližně osm let k plné spokojenosti. Ve výjimečných případech se může stát, že některý balík se pomocí distcc nezkompiluje, zatímco lokálně ano, v případě balíčků distribuce Gentoo je ale toto ošetřeno předem, takže portage pro takový balík distcc vypne. Pro správnou funkci je ale potřeba držet na všech strojích shodné verze GCC, alespoň co se týče první a druhé číslice. Pokud takhle podmínka není splněna, můžou se začít dít divné věci, včetně náhodných pádů aplikace.

Kromě distcc jsem určitou dobu používal i nástroj ccache, který má zrychlovat opakovanou kompilaci tak, že kešuje objektové soubory. Jeho přínos je velmi diskutabilní, navíc chyby jím způsobené nejsou na první pohled zřejmé. Proto jsem jej používat přestal.

Další zdroje

Byl pro vás článek přínosný?

Autor článku

Ondřej Caletka vystudoval obor Telekomunikační technika na ČVUT a dnes pracuje ve vzdělávacím oddělení RIPE NCC, mezinárodní asociaci koordinující internetové sítě.