&Notepad

Will Crichton – 9 de setembro de 2018

>

Eu tenho uma queixa com a frase “programação de sistemas”. Para mim, sempre pareceu combinar desnecessariamente duas idéias: programação de baixo nível (lidando com detalhes de implementação da máquina) e projeto de sistemas (criando e gerenciando um conjunto complexo de componentes interoperacionais). Por que é esse o caso? Há quanto tempo isso é verdade? E o que poderíamos ganhar se redefiníssemos a idéia de sistemas?

1970s: Melhorando na montagem

Vamos voltar às origens dos sistemas informáticos modernos para entender como o termo evoluiu. Não sei quem cunhou a frase originalmente, mas as minhas pesquisas sugerem que um esforço sério na definição de “sistemas de computador” começou por volta do início dos anos 70. Em Systems Programming Languages (Bergeron1 et al. 1972), os autores dizem:

A system program is an integrated set of subprograms,together forming a whole greater than the sum of its parts, andexceeding some threshold of size and/or complexity. Exemplos típicos de sistemas de multiprogramação, tradução, simulação, gerenciamento de informações e compartilhamento de tempo. O seguinte é um conjunto parcial de propriedades, algumas das quais são encontradas em não sistemas, nem todas precisam estar presentes em um determinado sistema.

  1. O problema a ser resolvido é de natureza ampla e consiste em muitos, e geralmente bastante variados, sub-problemas.
  2. O programa do sistema provavelmente será usado para suportar outros programas de software e aplicativos, mas também pode ser um pacote completo de aplicativos em si.
  3. É projetado para uso contínuo de “produção” ao invés de uma solução de uma só vez para um único problema de aplicativos.
  4. É provável que ele esteja evoluindo continuamente no número e tipos de características que ele suporta.
  5. Um programa de sistema requer uma certa disciplina ou estrutura, bothwithin e entre módulos (i.e. , “comunicação”) , e é normalmente projetado e implementado por mais de uma pessoa.

Esta definição é bastante agradável – sistemas de computador são de larga escala, de longa utilização e de tempo variável. Entretanto, embora esta definição seja amplamente descritiva, uma idéia chave no trabalho é prescritiva: defender a separação de linguagens de baixo nível das linguagens de sistemas (na época, contrastando montagem com FORTRAN).

O objetivo de uma linguagem de programação de sistemas é fornecer uma linguagem que possa ser usada sem preocupação indevida com considerações de “bit twiddling”, mas que gere código que não seja sensivelmente pior do que o gerado à mão. Tal linguagem deve combinar a concisão e a legibilidade de linguagens de alto nível com a eficiência de espaço e tempo e a capacidade de “chegar” às instalações da máquina e do sistema operacional que podem ser obtidas em linguagem assembler. O tempo de desenho, escrita e depuração deve ser beminimizado sem impor custos desnecessários aos recursos do sistema.

Ao mesmo tempo, pesquisadores da CMU publicaram BLISS: A Language for Systems Programming (Wulf et al. 1972), descrevendo-o como:

Referimo-nos a BLISS como uma “linguagem de implementação”, embora admitamos que o termo é um tanto ambíguo, uma vez que, presumivelmente, todas as linguagens de computador são usadas para implementar algo. Para nós, a frase conota uma linguagem de propósito geral, de nível superior, na qual a ênfase primária foi colocada numa aplicação específica, nomeadamente a escrita de grandes sistemas de software de produção para uma máquina específica. Linguagens de propósito especial, tais como compiladores-compiladores, não se enquadram nesta categoria, nem assumimos necessariamente que estas linguagens precisam ser independentes da máquina. Nós sublinhamos a palavra “implementação” na nossa definição e não utilizamos palavras como “design” e “documentação”. Não esperamos necessariamente que uma linguagem de implementação seja um veículo apropriado para expressar o projeto inicial de um sistema de grande porte, nem para a documentação exclusiva desse sistema. Conceitos como independência da máquina, expressando o projeto e a implementação na mesma notação, autodocumentação e outros, são claramente objetivos desejáveis e são critérios pelos quais avaliamos várias linguagens.

Aqui, os autores contrastam a idéia de uma “linguagem de implementação” como sendo de nível mais alto que a montagem, mas de nível mais baixo que uma “linguagem de projeto”. Isto resiste à definição do artigo anterior, defendendo que o projeto de um sistema e a implementação de um sistema deve ter linguagens separadas.

Alguns destes artigos são artefatos de pesquisa ou advocacias. A última entrada a considerar (também de 1972, um ano produtivo!) é Programação de Sistemas (Donovan 1972), um texto educacional para programação de sistemas de aprendizagem.

O que é programação de sistemas? Você pode visualizar um computador como uma espécie de besta que obedece a todos os comandos. Tem sido dito que computadores são basicamente pessoas feitas de metal ou, inversamente, pessoas são computadores feitos de carne e osso. No entanto, uma vez que nos aproximamos dos computadores, vemos que são basicamente máquinas que seguem instruções muito específicas e primitivas. Nos primeiros tempos dos computadores, as pessoas comunicavam-se com eles através de interruptores de ligar e desligar, denotando instruções primitivas. Logo as pessoas queriam dar instruções mais complexas. Por exemplo, elas queriam poder dizer X = 30 * Y; dado que Y = 10, o que é X? Hoje em dia os computadores não conseguem entender tal linguagem sem a ajuda de programas de sistemas. Os programas de sistemas (por exemplo, compiladores, carregadores, processadores de macro, sistemas operacionais) foram desenvolvidos para que os computadores se adaptassem melhor às necessidades dos seus utilizadores. Além disso, as pessoas queriam mais ajuda na mecânica de preparar seus programas.

I like that this definition remind us that systems are in service of people, even if they are just infrastructure not directly exposed to the end user.

1990s: O aumento do scripting

Nos anos 70 e 80, parece que a maioria dos pesquisadores via a programação de sistemas normalmente como um contraste com a programação assembly. Simplesmente não existiam outras boas ferramentas para construir sistemas. (Eu não tenho certeza onde Lisp estava em tudo isso? Nenhum dos recursos que li citou Lisp, embora eu esteja vagamente ciente de que as máquinas Lisp existiam, no entanto, brevemente.)

No entanto, em meados dos anos 90, uma grande mudança ocorreu nas linguagens de programação com o surgimento de linguagens de script dinâmicas. Melhorando em sistemas de shell scripting anteriores como Bash, linguagens como Perl (1987), Tcl (1988), Python (1990), Ruby (1995), PHP (1995), e Javascript (1995) trabalharam seu caminho para o mainstream. Isto culminou com o influente artigo “Scripting: Higher Level Programming for the 21st Century” (Ousterhout 1998). Esta articulada “dicotomia de Ousterhout” entre “linguagens de programação de sistemas” e “linguagens de scripting”

Linguagens de scripting são desenhadas para tarefas diferentes das linguagens de programação de sistemas, e isto leva a diferenças fundamentais nas linguagens. As linguagens de programação de sistemas foram desenhadas para construir estruturas de dados e algoritmos a partir do zero, a partir dos elementos computacionais mais primitivos, como palavras de memória. Em contraste, as linguagens de scripting são projetadas para colagem: elas assumem a existência de um conjunto de componentes poderosos e se destinam principalmente a conectar componentes em conjunto. As linguagens de programação de sistemas são fortemente digitadas para ajudar a gerenciar a complexidade, enquanto as linguagens de scripting são sem digitação para simplificar as conexões entre os componentes e fornecer um rápido desenvolvimento de aplicações. Várias tendências recentes, tais como máquinas mais rápidas, melhores linguagens de scripting, a crescente importância das interfaces gráficas de usuário e arquiteturas de componentes, e o crescimento da Internet, aumentaram muito a aplicabilidade das linguagens de scripting.

A um nível técnico, Ousterhout contrastou scripting vs. sistemas ao longo dos eixos de segurança de tipo e instruções por declaração, como mostrado acima. Em um nível de design, ele caracterizou os novos papéis para cada classe de linguagem: a programação de sistemas é para criar componentes, e o scripting é para colá-los juntos.

Conteúdo desta vez, as linguagens estaticamente datilografadas mas coletadas de lixo também começaram a ganhar popularidade. Java (1995) e C# (2000) se tornaram os titãs que conhecemos hoje. Embora estas duas não sejam tradicionalmente consideradas “linguagens de programação de sistemas”, elas têm sido usadas para projetar muitos dos maiores sistemas de software do mundo. Ousterhout até mencionou explicitamente “no mundo da Internet que está tomando forma agora, Java é usado para programação de sistemas”

2010s: Boundaries blur

Na última década, a linha entre as linguagens de scripting e as linguagens de programação de sistemas começou a desfocar-se. Empresas como a Dropbox foram capazes de construir sistemas surpreendentemente grandes e escaláveis em apenas Python. O Javascript é usado para renderizar UIs complexas e em tempo real em bilhões de páginas web. A digitação gradual ganhou vapor em Python, Javascript e outras linguagens de scripting, permitindo uma transição de código “protótipo” para código de “produção”, adicionando informações de tipo estático de forma incremental.

Ao mesmo tempo, recursos maciços de engenharia despejados em compiladores JIT tanto para linguagens estáticas (ex.: Java’s HotSpot) quanto para linguagens dinâmicas (ex.: LuaJIT de Lua, V8 de Javascript, Python’s PyPy) tornaram sua performance competitiva com as linguagens tradicionais de programação de sistemas (C, C++). Sistemas distribuídos em larga escala como Spark são escritos em Scala2. Novas linguagens de programação como Julia, Swift, e Go continuam a empurrar os limites de performance em linguagens coletadas em lixo.

Um painel chamado Programação de Sistemas em 2014 e Beyond apresentou as maiores mentes por trás das linguagens de sistemas auto-identificados de hoje: Bjarne Stroustrup (criador do C++), Rob Pike (criador do Go), Andrei Alexandrescu (criador do D), e Niko Matsakis (criador do Rust). Quando perguntados “o que é uma linguagem de programação de sistemas em 2014”, eles disseram (transcrição editada):

  • Niko Matsakis: Escrevendo aplicações do lado do cliente. O oposto polar daquilo para o qual Go está projetado. Nestas aplicações, você tem necessidades de alta latência, requisitos de alta segurança, muitos requisitos que não aparecem no lado do servidor.
  • Bjarne Stroustrup: A programação de sistemas saiu do campo onde você tinha que lidar com hardware, e então as aplicações se tornaram mais complicadas. Você precisa lidar com a complexidade. Se você tem algum problema de restrições significativas de recursos, você está no domínio da programação de sistemas. Se você precisa de um controle mais fino, então você também está no domínio da programação de sistemas. São as restrições que determinam se é programação de sistemas. Você está ficando sem memória? Está a ficar sem tempo?
  • Rob Pike: Quando anunciámos Go pela primeira vez, chamámos-lhe uma linguagem de programação de sistemas, e eu lamento um pouco porque muitas pessoas assumiram que era uma linguagem de escrita de sistemas operativos. O que nós deveríamos ter chamado é uma linguagem de escrita de servidores, que é o que nós realmente pensamos dela. Agora eu entendo que o que temos é uma linguagem de infraestrutura de nuvem. Outra definição de programação de sistemas é o que roda na nuvem.
  • Andrei Alexandrescu: Eu tenho alguns testes de tornassol para verificar se algo é uma linguagem de programação de sistemas. Uma linguagem de programação de sistemas deve ser capaz de permitir que você escreva seu próprio alocador de memória nela. Você deve ser capaz de forjar um número em um ponteiro, já que é assim que o hardware funciona.

A programação de sistemas é sobre alta performance então? Restrições de recursos? Controle de Hardware? Infra-estrutura da nuvem? Parece que, em termos gerais, as linguagens na categoria C, C++, Rust e D se distinguem em termos do seu nível de abstração da máquina. Essas linguagens expõem detalhes do hardware subjacente como alocação/layout de memória e gerenciamento de recursos de granulação fina.

Outra maneira de pensar sobre isso: quando você tem um problema de eficiência, quanta liberdade você tem para resolvê-lo? A parte maravilhosa dos langauges de programação de baixo nível é que quando você identifica uma ineficiência, está dentro do seu poder eliminar o gargalo através de um controle cuidadoso sobre os detalhes da máquina. Vetorize esta instrução, redimensione essa estrutura de dados para mantê-la em cache, e assim por diante. Da mesma forma, tipos estáticos fornecem mais confiança3 como “estas duas coisas que estou tentando adicionar são definitivamente inteiros”, linguagens de baixo nível fornecem mais confiança de que “este código será executado na máquina como eu especifiquei”

Por contraste, otimizar as linguagens interpretadas é uma selva absoluta. É incrivelmente difícil saber se o tempo de execução irá executar seu código consistentemente da maneira que você espera. Esta é exatamente a mesma questão com compiladores auto-paralelizados – “auto-vetorização não é um modelo de programação” (veja A história do ispc). É como escrever uma interface em Python, pensando “bem, eu certamente espero que quem chama esta função me dê uma int.”

Today: …então o que é programação de sistemas?

Isto traz-me de volta à minha gripe original. O que muitas pessoas chamam de programação de sistemas, eu penso em programação de baixo nível expondo os detalhes da máquina. Mas e os sistemas, então? Lembre-se da nossa definição de 1972:

  1. O problema a ser resolvido é de uma natureza ampla que consiste em muitos, e normalmente bastante variados, sub-problemas.
  2. O programa do sistema provavelmente será usado para suportar outros programas de software e aplicativos, mas também pode ser um pacote completo de aplicativos em si.
  3. É projetado para uso contínuo de “produção” em vez de uma solução de uma só vez para um único problema de aplicativos.
  4. É provável que ele esteja evoluindo continuamente no número e tipos de características que ele suporta.
  5. Um programa de sistema requer uma certa disciplina ou estrutura, bothwithin e entre módulos (i.e. , “comunicação”) , e é normalmente projetado e implementado por mais de uma pessoa.

Parecem muito mais problemas de engenharia de software (modularidade, reutilização, evolução do código) do que problemas de baixo nível de performance. O que significa que qualquer linguagem de programação que priorize a abordagem destes problemas é uma linguagem de programação de sistemas! Isso ainda não significa que toda linguagem seja uma linguagem de programação de sistemas. Linguagens de programação dinâmicas ainda estão indiscutivelmente longe das linguagens de sistemas, uma vez que tipos dinâmicos e expressões idiomáticas como “pedir perdão, não permissão” não são conducentes a uma boa qualidade de código.

O que esta definição nos leva, então? Aqui está uma boa ideia: linguagens funcionais como OCaml e Haskell são muito mais orientadas para sistemas do que linguagens de baixo nível como C ou C++. Quando ensinamos programação de sistemas a graduados, devemos incluir princípios de programação funcional como o valor da imutabilidade, o impacto de sistemas do tipo rico em melhorar o design da interface e a utilidade de funções de ordem superior. As escolas devem ensinar tanto programação de sistemas quanto programação de baixo nível.

Como defendido, existe uma distinção entre programação de sistemas e boa engenharia de software? Na verdade não, mas um problema aqui é que a engenharia de software e programação de baixo nível são muitas vezes ensinadas isoladamente. Enquanto a maioria das aulas de engenharia de software são geralmente centradas em Java “escrevem boas interfaces e testes”, nós também devemos ensinar aos alunos como projetar sistemas que têm restrições significativas de recursos. Talvez chamemos de “sistemas” de programação de baixo nível porque muitos dos sistemas de software mais interessantes são de baixo nível (por exemplo, bancos de dados, redes, sistemas operacionais, etc.). Como os sistemas de baixo nível têm muitas restrições, eles requerem que os seus designers pensem criativamente.

Outro enquadramento é que os programadores de baixo nível devem procurar compreender que ideias no design de sistemas poderiam ser adaptadas para lidar com a realidade do hardware moderno. Eu acho que a comunidade Rust tem sido extremamente inovadora neste aspecto, olhando para como bons princípios de design/funcional de software podem ser aplicados a problemas de baixo nível (por exemplo, futuros, manipulação de erros ou, claro, segurança de memória).

Para resumir, o que nós chamamos de “programação de sistemas” eu acho que deveria ser chamado de “programação de baixo nível”. O projeto de sistemas de computador como um campo é muito importante para não ter seu próprio nome. Separar claramente estas duas idéias fornece uma maior clareza conceitual sobre o espaço do design da linguagem de programação, e também abre a porta para compartilhar insights através dos dois espaços: como podemos projetar o sistema ao redor da máquina, e vice-versa?

Por favor, envie comentários diretos para minha caixa de entrada em [email protected] ou Hacker News.

  1. Fato frio aqui: dois dos autores deste artigo, R. Bergeron e Andy Van Dam, são membros fundadores da comunidade gráfica e da conferência SIGGRAPH. Parte de um padrão contínuo onde os pesquisadores gráficos definem a tendência no design de sistemas, c.f. a origem da GPGPU.

  2. Ligação obrigatória à Escalabilidade! Mas a que CUSTO?…

  3. Tipos estáticos são 100% garantias (ou o seu dinheiro de volta), mas na prática a maioria dos idiomas permitem alguma quantidade de Obj.magic.

Deixe uma resposta

O seu endereço de email não será publicado.