&Notepad

Will Crichton – September 9, 2018
Jeg har et problem med udtrykket “systemprogrammering”. For mig har det altid virket som en unødvendig kombination af to idéer: programmering på lavt niveau (beskæftiger sig med implementeringsdetaljer i maskinen) og systemdesign (skaber og administrerer et komplekst sæt af interoperable komponenter). Hvorfor er det tilfældet? Hvor længe har det været tilfældet? Og hvad kan vi få ud af at omdefinere systembegrebet?

1970’erne: Forbedring af assemblage

Lad os rejse tilbage til oprindelsen af moderne computersystemer for at forstå, hvordan begrebet har udviklet sig. Jeg ved ikke, hvem der oprindeligt opfandt udtrykket, men mine søgninger tyder på, at man begyndte at gøre en seriøs indsats for at definere “computersystemer” omkring begyndelsen af 70’erne. I Systems Programming Languages (Bergeron1 et al. 1972) siger forfatterne:

Et systemprogram er et integreret sæt af underprogrammer, som tilsammen udgør en helhed, der er større end summen af delene, og som overskrider en vis tærskel for størrelse og/eller kompleksitet. Typiske eksempler er systemer til multiprogrammering, oversættelse, simulering, informationsstyring og tidsdeling. Nedenstående er et delvist sæt af egenskaber, hvoraf nogle findes i ikke-systemer, og som ikke alle behøver at være til stede i et givet system.

  1. Det problem, der skal løses, er af bred karakter og består af mange og normalt ret forskellige delproblemer.
  2. Systemprogrammet vil sandsynligvis blive brugt til at understøtte andre software- og applikationsprogrammer, men kan også selv være en komplet applikationspakke.
  3. Det er designet til fortsat “produktionsbrug” snarere end til en engangs-løsning på et enkelt applikationsproblem.
  4. Det vil sandsynligvis udvikle sig løbende med hensyn til antallet og typerne af funktioner, det understøtter.
  5. Et systemprogram kræver en vis disciplin eller struktur, både inden for og mellem moduler (dvs. “kommunikation”), og er normalt designet og implementeret af mere end én person.

Denne definition er ret acceptabel – computersystemer er store, langvarigt anvendte og tidsvarierende. Men selv om denne definition i vid udstrækning er beskrivende, er en nøgleidé i artiklen normativ: den går ind for at adskille lavniveausprog fra systemsprog (på daværende tidspunkt blev assembler og FORTRAN sammenlignet).

Målet med et systemprogrammeringssprog er at levere et sprog, som kan anvendes uden unødig bekymring for “bit-twiddling”-hensyn, men som alligevel vil generere kode, der ikke er væsentligt dårligere end den kode, der genereres i hånden. Et sådant sprog bør kombinere højniveausprogs kortfattethed og læsbarhed med den plads- og tidseffektivitet og den evne til at “få adgang til” maskin- og operativsystemfaciliteter, der kan opnås i assembler-sprog. Tiden til design, skrivning og fejlfinding bør minimeres uden at påføre systemressourcerne unødigt overhead.

På samme tid udgav forskere fra CMU BLISS: A Language for Systems Programming (Wulf et al. 1972) og beskrev det som:

Vi betegner BLISS som et “implementeringssprog”, selv om vi indrømmer, at udtrykket er noget tvetydigt, da alle computersprog formodentlig bruges til at implementere noget. For os refererer udtrykket til et generelt sprog på højere niveau, hvor den primære vægt er lagt på en specifik anvendelse, nemlig skrivning af store produktionssoftware-systemer til en specifik maskine. Sprog til særlige formål, som f.eks. compiler-compilere, falder ikke ind under denne kategori, og vi antager heller ikke nødvendigvis, at disse sprog behøver at være maskinuafhængige. Vi lægger vægt på ordet “implementering” i vores definition og har ikke brugt ord som “design” og “dokumentation”. Vi forventer ikke nødvendigvis, at et implementeringssprog vil være et passende middel til at udtrykke det oprindelige design af et stort system eller til den eksklusive dokumentation af dette system. Begreber som maskinuafhængighed, at udtrykke design og implementering i samme notation, selvdokumentation og andre er klart ønskelige mål og er kriterier, som vi har evalueret de forskellige sprog ud fra.

Her stiller forfatterne ideen om et “implementeringssprog” som værende på et højere niveau end assembler, men på et lavere niveau end et “designsprog” i modsætning til et “implementeringssprog”. Dette modsætter sig definitionen i den foregående artikel, som går ind for, at design af et system og implementering af et system bør have separate sprog.

Både disse artikler er forskningsartefakter eller anbefalinger. Den sidste post at overveje (også fra 1972, et produktivt år!) er Systems Programming (Donovan 1972), en pædagogisk tekst til læring af systemprogrammering.

Hvad er systemprogrammering? Man kan forestille sig en computer som en slags bæst, der adlyder alle kommandoer. Det er blevet sagt, at computere i bund og grund er mennesker lavet af metal, eller omvendt er mennesker computere lavet af kød og blod. Men når vi kommer tæt på computere, kan vi se, at de grundlæggende er maskiner, der følger meget specifikke og primitive instruktioner. I computernes tidlige dage kommunikerede man med dem ved hjælp af tænd- og slukkontakter, der betød primitive instruktioner. Snart ønskede man at give mere komplekse instruktioner. F.eks. ønskede man at kunne sige X = 30 * Y; hvis Y = 10, hvad er så X? Nutidens computere kan ikke forstå et sådant sprog uden hjælp fra systemprogrammer. Systemprogrammer (f.eks. compilere, loadere, makroprocessorer, operativsystemer) blev udviklet for at gøre computere bedre tilpasset brugernes behov. Endvidere ønskede folk mere hjælp til mekanikken i forbindelse med udarbejdelsen af deres programmer.

Jeg kan godt lide, at denne definition minder os om, at systemer er til tjeneste for mennesker, selv om de blot er infrastruktur, der ikke er direkte udsat for slutbrugeren.

1990’erne: I 70’erne og 80’erne ser det ud til, at de fleste forskere så systemprogrammering som regel som en modsætning til assemblerprogrammering. Der var simpelthen ikke andre gode værktøjer til at bygge systemer. (Jeg er ikke sikker på, hvor Lisp var i alt dette? Ingen af de ressourcer, jeg læste, citerede Lisp, selv om jeg er vagt klar over, at Lisp-maskiner eksisterede, om end kortvarigt.)

Men i midten af 90’erne skete der imidlertid en stor forandring i programmeringssprogene med fremkomsten af dynamisk typede scriptingsprog. Ved at forbedre tidligere shell-scripting-systemer som Bash arbejdede sprog som Perl (1987), Tcl (1988), Python (1990), Ruby (1995), PHP (1995) og Javascript (1995) sig ind i mainstream. Dette kulminerede i den indflydelsesrige artikel “Scripting: Higher Level Programming for the 21st Century” (Ousterhout 1998). Heri blev “Ousterhout’s dikotomi” mellem “systemprogrammeringssprog” og “scriptingsprog” formuleret.”

Scriptingsprog er designet til andre opgaver end systemprogrammeringssprog, og dette fører til grundlæggende forskelle i sprogene. Systemprogrammeringssprog blev designet til at opbygge datastrukturer og algoritmer fra bunden, med udgangspunkt i de mest primitive computerelementer som f.eks. ord i hukommelsen. Scripting-sprog er derimod designet til at klistre: de forudsætter, at der findes et sæt effektive komponenter, og er først og fremmest beregnet til at forbinde komponenterne med hinanden. Systemprogrammeringssprog er stærkt typede for at hjælpe med at styre kompleksiteten, mens scriptingsprog er typeløse for at forenkle forbindelserne mellem komponenterne og give mulighed for hurtig udvikling af applikationer. Flere nyere tendenser, såsom hurtigere maskiner, bedre scriptingsprog, den stigende betydning af grafiske brugergrænseflader og komponentarkitekturer samt internettets vækst, har i høj grad øget scriptingsprogenes anvendelighed.

På et teknisk plan satte Ousterhout scripting vs. systemer i kontrast til hinanden på akserne type-sikkerhed og instruktioner pr. sætning, som vist ovenfor. På designniveau karakteriserede han de nye roller for hver sprogklasse: systemprogrammering er til at skabe komponenter, og scripting er til at klistre dem sammen.

Omkring dette tidspunkt begyndte statisk typede, men garbage collected-sprog også at vinde popularitet. Java (1995) og C# (2000) blev til de titaner, som vi kender i dag. Selv om disse to ikke traditionelt betragtes som “systemprogrammeringssprog”, er de blevet brugt til at designe mange af verdens største softwaresystemer. Ousterhout nævnte endda udtrykkeligt “i den internetverden, der er ved at tage form nu, bruges Java til systemprogrammering.”

2010’erne: Grænserne udviskes

I det sidste årti er grænsen mellem scriptingsprog og systemprogrammeringssprog begyndt at udviskes. Virksomheder som Dropbox var i stand til at opbygge overraskende store og skalerbare systemer på blot Python. Javascript bruges til at gengive komplekse brugergrænseflader i realtid på milliarder af websider. Gradual typing har vundet frem i Python, Javascript og andre scriptingsprog, hvilket muliggør en overgang fra “prototype”-kode til “produktions”-kode ved gradvist at tilføje statiske typeoplysninger.

Samme tid har massive tekniske ressourcer, der er blevet brugt på JIT-compilere til både statiske sprog (f.eks. Javas HotSpot) og dynamiske sprog (f.eks. Luas LuaJIT, Javascript’s V8, Pythons PyPy), gjort deres ydeevne konkurrencedygtig med traditionelle systemprogrammeringssprog (C, C++). Distribuerede systemer i stor skala som Spark er skrevet i Scala2. Nye programmeringssprog som Julia, Swift og Go fortsætter med at flytte præstationsgrænserne for garbage-collected languages.

Et panel med titlen Systems Programming in 2014 and Beyond præsenterede de største hjerner bag nutidens selvbestaltede systemsprog: Bjarne Stroustrup (ophavsmand til C++), Rob Pike (ophavsmand til Go), Andrei Alexandrescu (D-udvikler) og Niko Matsakis (Rust-udvikler). På spørgsmålet “hvad er et systemprogrammeringssprog i 2014” svarede de (redigeret transskription):

  • Niko Matsakis: At skrive klient-side applikationer. Det stik modsatte af det, som Go er designet til. I disse applikationer har du behov for høj latenstid, høje sikkerhedskrav, en masse krav, som ikke dukker op på serversiden.
  • Bjarne Stroustrup: Jeg har været en af de mest avancerede programmører, der har arbejdet med hardware, og så er applikationerne blevet mere komplicerede. Man er nødt til at håndtere kompleksitet. Hvis du har problemer med betydelige ressourcebegrænsninger, er du inden for systemprogrammering. Hvis man har brug for en mere finkornet kontrol, er man også inden for systemprogrammering. Det er begrænsningerne, der afgør, om der er tale om systemprogrammering. Er du ved at løbe tør for hukommelse? Er du ved at løbe tør for tid?
  • Rob Pike: Da vi først annoncerede Go, kaldte vi det et systemprogrammeringssprog, og det beklager jeg lidt, fordi mange mennesker antog, at det var et sprog til at skrive operativsystemer. Vi burde have kaldt det et sprog til at skrive servere, hvilket er det, vi egentlig tænkte på det som. Nu forstår jeg, at det, vi har, er et cloud-infrastruktursprog. En anden definition af systemprogrammering er de ting, der kører i skyen.
  • Andrei Alexandrescu: Jeg har et par lakmusprøver til at tjekke, om noget er et systemprogrammeringssprog. Et systemprogrammeringssprog skal kunne give dig mulighed for at skrive din egen hukommelsesallokator i det. Du skal kunne forfalske et tal til en pointer, da det er sådan hardware fungerer.

Har systemprogrammering så at gøre med høj ydeevne? Ressourcebegrænsninger? Hardware kontrol? Cloud-infrastruktur? Det ser ud til, at sprog i kategorien C, C++, Rust og D i grove træk adskiller sig i forhold til deres abstraktionsniveau i forhold til maskinen. Disse sprog afslører detaljer i den underliggende hardware som f.eks. hukommelsesallokering/layout og finkornet ressourcestyring.

En anden måde at tænke på det på: Når du har et effektivitetsproblem, hvor meget frihed har du så til at løse det? Det vidunderlige ved programmeringssprog på lavt niveau er, at når du identificerer en ineffektivitet, ligger det i din magt at fjerne flaskehalsen ved hjælp af omhyggelig kontrol over maskindetaljer. Vektoriser denne instruktion, ændre størrelsen på denne datastruktur for at holde den i cache osv. På samme måde som statiske typer giver mere tillid3 som “disse to ting, jeg forsøger at tilføje, er helt sikkert hele tal”, giver lavniveausprog mere tillid til, at “denne kode vil blive udført på maskinen, som jeg har angivet.”

Det er derimod en absolut jungle at optimere fortolkede sprog. Det er utroligt svært at vide, om kørselstidspunktet konsekvent vil udføre din kode på den måde, du forventer. Det er nøjagtig det samme problem med auto-paralleliserende compilere – “auto-vektorisering er ikke en programmeringsmodel” (se Historien om ispc). Det svarer til at skrive en grænseflade i Python og tænke: “Nå, men jeg håber bestemt, at den, der kalder denne funktion, giver mig en int.”

I dag: …så hvad er systemprogrammering?

Dette bringer mig tilbage til min oprindelige anke. Det, som mange kalder systemprogrammering, opfatter jeg blot som programmering på lavt niveau – at afsløre detaljer i maskinen. Men hvad med systemer så? Husk vores definition fra 1972:

  1. Det problem, der skal løses, er af bred karakter og består af mange og normalt ret forskellige delproblemer.
  2. Systemprogrammet vil sandsynligvis blive brugt til at understøtte andre software- og applikationsprogrammer, men kan også selv være en komplet applikationspakke.
  3. Det er designet til fortsat “produktionsbrug” snarere end til en engangs-løsning på et enkelt applikationsproblem.
  4. Det vil sandsynligvis udvikle sig løbende med hensyn til antallet og typerne af funktioner, det understøtter.
  5. Et systemprogram kræver en vis disciplin eller struktur, både inden for og mellem moduler (dvs. “kommunikation”), og det er normalt designet og implementeret af mere end én person.

Dette ligner meget mere spørgsmål om softwareudvikling (modularitet, genbrug, kodeudvikling) end spørgsmål om ydeevne på lavt niveau. Hvilket betyder, at ethvert programmeringssprog, der prioriterer at tage fat på disse problemer, er et systemprogrammeringssprog! Det betyder stadig ikke, at ethvert sprog er et systemprogrammeringssprog. Dynamiske programmeringssprog er vel stadig langt fra systemsprog, da dynamiske typer og idiomer som “ask forgiveness, not permission” ikke er befordrende for god kodekvalitet.

Hvad giver denne definition os så? Her er et hot take: funktionelle sprog som OCaml og Haskell er langt mere systemorienterede end lavniveausprog som C eller C++. Når vi underviser studerende i systemprogrammering, bør vi inddrage funktionelle programmeringsprincipper som f.eks. værdien af uforanderlighed, virkningen af rige typesystemer til forbedring af grænsefladedesignet og nytten af funktioner af højere orden. Skolerne bør undervise i både systemprogrammering og programmering på lavt niveau.

Er der, som det anbefales, en forskel mellem systemprogrammering og god softwareudvikling? Egentlig ikke, men et problem her er, at der ofte undervises i softwareudvikling og programmering på lavt niveau isoleret fra hinanden. Mens de fleste kurser i software engineering normalt er Java-centreret “skriv gode grænseflader og tests”, bør vi også lære de studerende om, hvordan man designer systemer, der har betydelige ressourcebegrænsninger. Måske kalder vi programmering på lavt niveau for “systemer”, fordi mange af de mest interessante softwaresystemer er på lavt niveau (f.eks. databaser, netværk, styresystemer osv.). Da systemer på lavt niveau har mange begrænsninger, kræver de, at deres designere tænker kreativt.

En anden indramning er, at programmører på lavt niveau bør søge at forstå, hvilke idéer inden for systemdesign der kan tilpasses til at håndtere virkeligheden i moderne hardware. Jeg synes, at Rust-fællesskabet har været overordentlig innovativt i denne henseende, idet de har set på, hvordan gode principper for softwaredesign/funktionel programmering kan anvendes på problemer på lavt niveau (f.eks. futures, fejlhåndtering eller naturligvis hukommelsessikkerhed).

For at opsummere, mener jeg, at det, vi kalder “systemprogrammering”, bør kaldes “programmering på lavt niveau”. Computer systemdesign som et område er for vigtigt til ikke at have sit eget navn. En klar adskillelse af disse to idéer giver større begrebsmæssig klarhed om området for design af programmeringssprog, og det åbner også døren til at dele indsigt på tværs af de to områder: hvordan kan vi designe systemet omkring maskinen og omvendt?

Skriv venligst kommentarer til min indbakke på [email protected] eller Hacker News.

  1. Cool fact her: to af forfatterne til denne artikel, R. Bergeron og Andy Van Dam, er stiftende medlemmer af det grafiske samfund og SIGGRAPH-konferencen. En del af et vedvarende mønster, hvor grafikforskere sætter tendensen inden for systemdesign, jf. oprindelsen af GPGPU.

  2. Obligatorisk link til Scalability! Men til hvilken PRIS?.

  3. Ideelt er statiske typer en 100% garanti (eller pengene tilbage), men i praksis tillader de fleste sprog en vis mængde Obj.magic.

Skriv et svar

Din e-mailadresse vil ikke blive publiceret.