&Notepad

Will Crichton – 9. září 2018
Mám výtku ke slovnímu spojení „systémové programování“. Vždycky mi připadalo, že zbytečně spojuje dvě myšlenky: nízkoúrovňové programování (zabývající se implementačními detaily stroje) a návrh systémů (vytváření a správa složitého souboru vzájemně spolupracujících komponent). Proč tomu tak je? Jak dlouho to platí? A co bychom mohli získat, kdybychom znovu definovali myšlenku systémů?“

70. léta 20. století: Vraťme se k počátkům moderních počítačových systémů, abychom pochopili, jak se tento pojem vyvíjel. Nevím, kdo tento výraz původně vymyslel, ale z mého pátrání vyplývá, že vážné snahy o definici „počítačových systémů“ začaly někdy na počátku 70. let. V knize Systémové programovací jazyky (Bergeron1 a kol. 1972) autoři říkají:

Systémový program je integrovaná množina podprogramů,které dohromady tvoří celek větší než součet jeho částí a přesahují určitý práh velikosti a/nebo složitosti. Typickými příklady jsousystémy pro multiprogramování, překládání, simulaci, správu informací a sdílení času. Níže je uveden částečný soubor vlastností,z nichž některé se vyskytují i v nesystémech a ne všechny musí být v daném systému přítomny.

  1. Problém, který je třeba vyřešit, má širokou povahu a skládá se z mnoha,a obvykle dosti rozmanitých, dílčích problémů.
  2. Systémový program bude pravděpodobně sloužit k podpoře jiného softwarua aplikačních programů, ale může být také kompletním aplikačním balíkem sám o sobě.
  3. Je určen spíše pro trvalé „produkční“ použití než pro jednorázové řešení jednoho aplikačního problému.
  4. Je pravděpodobné, že se bude neustále vyvíjet v počtu a typech funkcí, které podporuje.
  5. Systémový program vyžaduje určitou disciplínu nebo strukturu, a to jak uvnitř modulů, tak mezi nimi (tj. ,,komunikaci“) , a je obvyklenavržen a implementován více než jednou osobou.

Tato definice je poměrně přijatelná – počítačové systémy jsou rozsáhlé, dlouhodobě používané a časově proměnlivé. Nicméně zatímco tato definice je do značné míry popisná, klíčová myšlenka v článku je normativní: obhajuje oddělení nízkoúrovňových jazyků od systémových jazyků (v té době kontrastuje assembler s FORTRANem).

Cílem systémového programovacího jazyka je poskytnout jazyk, který lze používat bez zbytečných obav z ohledů na „bit twiddling“, a přesto bude generovat kód, který není znatelně horší než kód generovaný ručně. Takový jazyk by měl kombinovat stručnost a čitelnost vysokoúrovňových jazyků s prostorovou a časovou efektivitou a schopností „dostat se“ k prostředkům stroje a operačního systému, které lze získat v jazyce assembler. Doba návrhu, zápisu a ladění by měla být minimalizována bez zbytečné zátěže systémových prostředků.

V téže době publikovali výzkumníci z CMU knihu BLISS: A Language for Systems Programming (Wulf et al. 1972), která jej popisuje jako:

My označujeme BLISS jako „implementační jazyk“, i když připouštíme, že tento termín je poněkud dvojznačný, protože pravděpodobně všechny počítačové jazyky se používají k implementaci něčeho. Pro nás tento výraz znamená jazyk vyšší úrovně pro všeobecné použití, u něhož byl hlavní důraz kladen na konkrétní aplikaci, konkrétně na psaní rozsáhlých produkčních softwarových systémů pro konkrétní stroj. Jazyky pro speciální účely, jako jsou překladače-kompilátory, do této kategorie nespadají a ani nutně nepředpokládáme, že tyto jazyky musí být nezávislé na stroji. V naší definici zdůrazňujeme slovo „implementace“ a nepoužili jsme slova jako „návrh“ a „dokumentace“. Nepředpokládáme nutně, že implementační jazyk bude vhodným prostředkem pro vyjádření počátečního návrhu rozsáhlého systému ani pro výhradní dokumentaci tohoto systému. Pojmy jako nezávislost na stroji, vyjádření návrhu a implementace ve stejné notaci, autodokumentace a další jsou jednoznačně žádoucími cíli a jsou kritérii, podle kterých jsme hodnotili různé jazyky.

Autoři zde staví do kontrastu představu „implementačního jazyka“ jako jazyka vyšší úrovně než assembler, ale nižší úrovně než „návrhový jazyk“. To odporuje definici v předchozím článku, který obhajuje, že návrh systému a implementace systému by měly mít oddělené jazyky.

Oba tyto články jsou výzkumnými artefakty nebo obhajobami. Poslední položka, kterou je třeba zvážit (rovněž z roku 1972, produktivního roku!), je Systems Programming (Donovan 1972), výukový text pro výuku systémového programování.

Co je to systémové programování? Počítač si můžete představit jako jakési zvíře, které poslouchá všechny příkazy. Říká se, že počítače jsou v podstatě lidé z kovu nebo naopak lidé jsou počítače z masa a kostí. Jakmile se však k počítačům dostaneme blíže, vidíme, že jsou to v podstatě stroje, které se řídí velmi specifickými a primitivními instrukcemi. V počátcích počítačů s nimi lidé komunikovali pomocí vypínačů označujících primitivní instrukce. Brzy chtěli lidé zadávat složitější instrukce. Chtěli například umět říci X = 30 * Y; vzhledem k tomu, že Y = 10, kolik je X? Současné počítače takovému jazyku bez pomoci systémových programů nerozumějí. Systémové programy (např. kompilátory, zavaděče, makroprocesory, operační systémy) byly vyvinuty proto, aby se počítače lépe přizpůsobily potřebám svých uživatelů. Dále lidé chtěli větší pomoc při mechanické přípravě svých programů.

Líbí se mi, že tato definice připomíná, že systémy slouží lidem, i když jsou jen infrastrukturou, která není přímo vystavena koncovému uživateli.

1990: V 70. a 80. letech jako by většina výzkumníků vnímala systémové programování obvykle jako protiklad k programování v assembleru. Jiné dobré nástroje pro tvorbu systémů prostě neexistovaly. (Nejsem si jistý, kde v tom všem byl Lisp? Žádný ze zdrojů, které jsem četl, Lisp neuváděl, ačkoli si matně uvědomuji, že stroje s Lispem existovaly, jakkoli krátce)

V polovině 90. let však došlo k velké změně v programovacích jazycích s nástupem dynamicky typovaných skriptovacích jazyků. Vylepšením dřívějších shell skriptovacích systémů jako Bash se do hlavního proudu propracovaly jazyky jako Perl (1987), Tcl (1988), Python (1990), Ruby (1995), PHP (1995) a Javascript (1995). To vyvrcholilo vydáním vlivného článku „Scripting: (Ousterhout 1998). V něm byla formulována „Ousterhoutova dichotomie“ mezi „systémovými programovacími jazyky“ a „skriptovacími jazyky“.

Skriptovací jazyky jsou určeny pro jiné úlohy než systémové programovací jazyky, a to vede k zásadním rozdílům v těchto jazycích. Systémové programovací jazyky byly navrženy pro vytváření datových struktur a algoritmů od nuly, počínaje nejprimitivnějšími prvky počítače, jako jsou slova paměti. Naproti tomu skriptovací jazyky jsou určeny ke slepování: předpokládají existenci sady výkonných komponent a jsou určeny především ke spojování komponent dohromady. Systémové programovací jazyky jsou silně typované, aby pomohly zvládnout složitost, zatímco skriptovací jazyky jsou beztypové, aby zjednodušily spojení mezi komponentami a umožnily rychlý vývoj aplikací. Několik nedávných trendů, jako jsou rychlejší stroje, lepší skriptovací jazyky, rostoucí význam grafických uživatelských rozhraní a komponentových architektur a rozvoj internetu, výrazně zvýšilo použitelnost skriptovacích jazyků.

Na technické úrovni postavil Ousterhout skriptovací a systémové jazyky do kontrastu podle osy typové bezpečnosti a instrukcí na příkaz, jak je uvedeno výše. Na návrhové úrovni charakterizoval nové role jednotlivých tříd jazyků: systémové programování slouží k vytváření komponent a skriptování k jejich slepování.

Přibližně v této době začaly získávat popularitu také staticky typované, ale garbage collected jazyky. Java (1995) a C# (2000) se proměnily v titány, které známe dnes. Ačkoli tyto dva jazyky nejsou tradičně považovány za „systémové programovací jazyky“, byly použity k návrhu mnoha největších softwarových systémů na světě. Ousterhout dokonce výslovně zmínil, že „ve světě internetu, který se nyní formuje, se Java používá pro systémové programování.“

2010: Hranice se stírají

V posledním desetiletí se hranice mezi skriptovacími jazyky a jazyky pro systémové programování začala stírat. Společnosti jako Dropbox dokázaly vytvořit překvapivě velké a škálovatelné systémy pouze v jazyce Python. Javascript se používá k vykreslování složitých uživatelských rozhraní v reálném čase na miliardách webových stránek. V jazycích Python, Javascript a dalších skriptovacích jazycích se prosadilo postupné typování, které umožňuje přechod od „prototypového“ kódu k „produkčnímu“ kódu postupným přidáváním statických typových informací.

Současně se obrovské inženýrské prostředky vložené do JIT kompilátorů jak pro statické jazyky (např. HotSpot v Javě), tak pro dynamické jazyky (např. LuaJIT v jazyce Lua, V8 v Javascriptu, PyPy v Pythonu) postaraly o to, že jejich výkon je konkurenceschopný s tradičními systémovými programovými jazyky (C, C++). Rozsáhlé distribuované systémy, jako je Spark, jsou napsány v jazyce Scala2. Nové programovací jazyky jako Julia, Swift a Go pokračují v posouvání výkonnostních hranic jazyků se sběrem odpadků.

V panelu nazvaném Systémové programování v roce 2014 a dále vystoupily největší mozky, které stojí za současnými sebeidentifikovanými systémovými jazyky: Bjarne Stroustrup (tvůrce C++), Rob Pike (tvůrce Go), Andrei Alexandrescu (vývojář D) a Niko Matsakis (vývojář Rustu). Na otázku „co je systémový programovací jazyk v roce 2014“ odpověděli (upravený přepis):

  • Niko Matsakis: Psaní aplikací na straně klienta. Polární opak toho, k čemu je Go určeno. V těchto aplikacích máte vysoké nároky na latenci, vysoké nároky na bezpečnost, spoustu požadavků, které se na straně serveru nevyskytují.“
  • Bjarne Stroustrup: Systémové programování vzešlo z oblasti, kde jste se museli zabývat hardwarem, a pak se aplikace staly složitějšími. Musíte se vypořádat se složitostí. Pokud máte nějaké problémy s výrazným omezením zdrojů, jste v oblasti systémového programování. Pokud potřebujete jemnější řízení, pak jste také v oblasti systémového programování. O tom, zda se jedná o systémové programování, rozhodují omezení. Dochází vám paměť? Dochází vám čas?
  • Rob Pike: Když jsme Go poprvé oznámili, nazvali jsme ho systémovým programovacím jazykem a trochu toho lituji, protože spousta lidí předpokládala, že jde o jazyk pro psaní operačních systémů. Měli jsme ho nazvat spíše jazykem pro psaní serverů, což je to, co jsme si o něm skutečně mysleli. Nyní chápu, že to, co máme, je jazyk pro cloudovou infrastrukturu. Další definice systémového programování je věc, která běží v cloudu.“
  • Andrei Alexandrescu: Mám několik lakmusových papírků pro kontrolu, zda je něco systémový programovací jazyk. Systémový programovací jazyk musí umožňovat napsat si v něm vlastní alokátor paměti. Měl by umět zfalšovat číslo na ukazatel, protože tak funguje hardware.“

Jde tedy při systémovém programování o vysoký výkon? Omezení zdrojů? O řízení hardwaru? Infrastruktura cloudu? Zdá se, že obecně vzato se jazyky kategorie C, C++, Rust a D liší úrovní abstrakce od stroje. Tyto jazyky odhalují detaily podkladového hardwaru, jako je alokace/rozložení paměti a jemná správa zdrojů.

Jiný způsob, jak o tom přemýšlet: Když máte problém s efektivitou, kolik volnosti máte při jeho řešení? Nádhera nízkoúrovňových programovacích jazyků spočívá v tom, že když identifikujete neefektivitu, je ve vaší moci odstranit úzké místo pečlivou kontrolou strojových detailů. Vektorizujte tuto instrukci, změňte velikost této datové struktury, aby zůstala v cache, a tak dále. Stejně jako statické typy poskytují větší jistotu3 typu „tyto dvě věci, které se snažím sečíst, jsou určitě celá čísla“, nízkoúrovňové jazyky poskytují větší jistotu, že „tento kód se na stroji provede tak, jak jsem zadal.“

Oproti tomu optimalizace interpretovaných jazyků je naprostá džungle. Je neuvěřitelně těžké zjistit, zda runtime bude konzistentně vykonávat váš kód tak, jak očekáváte. To je přesně stejný problém jako u automaticky paralelizujících překladačů – „automatická vektorizace není programovací model“ (viz Příběh ispc). Je to jako psát rozhraní v Pythonu a myslet si: „No, rozhodně doufám, že ten, kdo tuto funkci zavolá, mi dá int.“

Dnes: …tak co je to systémové programování?“

Tím se vracím ke své původní výtce. To, čemu mnoho lidí říká systémové programování, já považuji jen za nízkoúrovňové programování – odhalování detailů stroje. Ale co potom systémy? Vzpomeňte si na naši definici z roku 1972:

  1. Problém, který je třeba vyřešit, má širokou povahu a skládá se z mnoha a obvykle poměrně rozmanitých dílčích problémů.
  2. Systémový program bude pravděpodobně sloužit k podpoře jiných softwarových a aplikačních programů, ale může být také kompletním aplikačním balíčkem sám o sobě.
  3. Je určen spíše pro trvalé „produkční“ použití než pro jednorázové řešení jednoho aplikačního problému.
  4. Je pravděpodobné, že se bude neustále vyvíjet v počtu a typech funkcí, které podporuje.
  5. Systémový program vyžaduje určitou disciplínu nebo strukturu, a to jak uvnitř modulů, tak mezi nimi (tj. ,,komunikaci“) , a je obvyklenavrhován a implementován více než jednou osobou.

Tyto otázky se zdají být mnohem podobnější otázkám softwarového inženýrství (modularita, opakované použití, vývoj kódu) než otázkám nízké výkonnosti. Což znamená, že každý programovací jazyk, který prioritně řeší tyto problémy, je systémový programovací jazyk! To ale stále neznamená, že každý jazyk je systémový programovací jazyk. Dynamické programovací jazyky mají pravděpodobně stále daleko k systémovým jazykům, protože dynamické typy a idiomy jako „žádej o odpuštění, ne o povolení“ nepřispívají k dobré kvalitě kódu.

K čemu nám tedy tato definice vede? Tady je horká myšlenka: funkcionální jazyky jako OCaml a Haskell jsou mnohem více systémově orientované než nízkoúrovňové jazyky jako C nebo C++. Při výuce systémového programování pro vysokoškolské studenty bychom měli zahrnout principy funkcionálního programování, jako je hodnota neměnnosti, vliv bohatých typových systémů na zlepšení návrhu rozhraní a užitečnost funkcí vyššího řádu. Školy by měly vyučovat jak systémové programování, tak nízkoúrovňové programování.

Jak je obhajováno, existuje rozdíl mezi systémovým programováním a dobrým softwarovým inženýrstvím? Ne tak docela, ale problémem je, že softwarové inženýrství a nízkoúrovňové programování se často vyučují odděleně. Zatímco většina hodin softwarového inženýrství je obvykle zaměřena na Javu „napište dobrá rozhraní a testy“, měli bychom studenty učit také o tom, jak navrhovat systémy, které mají značná omezení zdrojů. Možná, že nízkoúrovňovému programování říkáme „systémy“, protože mnoho nejzajímavějších softwarových systémů je nízkoúrovňových (např. databáze, sítě, operační systémy atd.). Protože nízkoúrovňové systémy mají mnoho omezení, vyžadují od svých návrhářů kreativní myšlení.

Další zarámování spočívá v tom, že nízkoúrovňoví programátoři by se měli snažit pochopit, jaké myšlenky v oblasti návrhu systémů by mohly být přizpůsobeny realitě moderního hardwaru. Myslím, že komunita Rustu je v tomto ohledu mimořádně inovativní a hledá, jak lze dobré principy návrhu softwaru/funkčního programování aplikovat na nízkoúrovňové problémy (např. futures, ošetřování chyb nebo samozřejmě bezpečnost paměti).

Shrnuto a podtrženo, to, čemu říkáme „systémové programování“, by se podle mě mělo nazývat „nízkoúrovňové programování“. Návrh počítačových systémů jako obor je příliš důležitý na to, aby neměl své vlastní jméno. Jasné oddělení těchto dvou myšlenek poskytuje větší koncepční jasnost v prostoru návrhu programovacích jazyků a také otevírá dveře ke sdílení poznatků napříč těmito dvěma prostory: jak můžeme navrhovat systém kolem stroje a naopak?“

Připomínky prosím směřujte do mé schránky na [email protected] nebo na Hacker News.

  1. Skvělý fakt: dva z autorů tohoto článku, R. Bergeron a Andy Van Dam, jsou zakládajícími členy grafické komunity a konference SIGGRAPH. Součást pokračujícího vzorce, kdy výzkumníci v oblasti grafiky určují trend v návrhu systémů, viz vznik GPGPU.

  2. Povinný odkaz na Škálovatelnost! Ale za jakou CENU?

  3. Samozřejmě statické typy jsou 100% zárukou (nebo vám vrátí peníze), ale v praxi většina jazyků určitou míru obj. magie umožňuje.

Napsat komentář

Vaše e-mailová adresa nebude zveřejněna.