No entanto, C é uma linguagem de baixo nível


Na última década, desde o advento da linguagem C, muitas linguagens de programação interessantes foram criadas. Alguns deles ainda são usados, outros influenciaram a próxima geração de idiomas, a popularidade do terceiro desapareceu silenciosamente. Enquanto isso, arcaico, polêmico, primitivo, tornava as piores tradições de sua geração de línguas C (e seus herdeiros) mais vivas do que todos os seres vivos.


Criticism C é um gênero epistolar clássico para nossa indústria. Parece mais alto, depois mais silencioso, mas ultimamente tem sido literalmente impressionante. Um exemplo é uma tradução do artigo de David Ciswell “C não é um idioma de baixo nível”, publicado em nosso blog há algum tempo. Você pode dizer coisas diferentes sobre C, há realmente muitos erros desagradáveis ​​no design da linguagem, mas recusar C no "nível baixo" é demais!


Para não tolerar tal injustiça, tomei coragem e tentei decidir o que era uma linguagem de programação de baixo nível e quais práticas eles queriam dela. Depois disso, examinei os argumentos dos críticos C. Foi assim que este artigo foi publicado.


Conteúdo



Argumentos de crítica C


Aqui estão alguns dos argumentos dos críticos de C, incluindo os listados em um artigo de David Chiznell:


  1. A máquina abstrata da linguagem C é muito semelhante à arquitetura desatualizada do PDP-11, que há muito tempo deixa de corresponder ao dispositivo dos populares processadores modernos.
  2. A incompatibilidade entre uma máquina abstrata e o dispositivo de máquinas reais complica o desenvolvimento de otimizadores de compiladores de linguagem.
  3. A incompletude e a complexidade do padrão de idioma levam a discrepâncias nas implementações padrão.
  4. O domínio das linguagens do tipo C não permite explorar arquiteturas alternativas de processadores.

Vamos primeiro determinar os requisitos para uma linguagem de baixo nível, após o que retornamos aos argumentos fornecidos.


Linguagem de programação de baixo nível


Não existe uma definição universalmente aceita de uma linguagem de baixo nível. Mas antes de discutir questões controversas, é desejável ter pelo menos alguns requisitos iniciais para o assunto da disputa.


Ninguém argumentará que a linguagem assembly está no nível mais baixo. Mas em cada plataforma é único, portanto, o código em um idioma assim não pode ser portátil. Mesmo em uma plataforma compatível com versões anteriores, pode ser necessário usar algumas novas instruções.


A partir daqui segue o primeiro requisito para um idioma de baixo nível: ele deve manter recursos comuns para plataformas populares . Simplificando, o compilador deve ser portátil. A portabilidade do compilador simplifica o desenvolvimento de compiladores de linguagem para novas plataformas, e a variedade de plataformas suportadas por compiladores elimina a necessidade de desenvolvedores reescreverem programas de aplicativos para cada nova máquina.


O primeiro requisito entra em conflito com os desejos dos desenvolvedores de programas especiais: linguagens de programação, drivers, sistemas operacionais e bancos de dados de alto desempenho. Os programadores que escrevem esses programas desejam otimizar manualmente, trabalhar diretamente com a memória e assim por diante. Em uma palavra, uma linguagem de baixo nível deve permitir trabalhar com os detalhes da implementação da plataforma .


Encontrar um equilíbrio entre esses dois requisitos - identificar aspectos comuns às plataformas e acessar o máximo de detalhes possível - é um motivo fundamental para a dificuldade de desenvolver uma linguagem de baixo nível.


Observe que abstrações de alto nível não são tão importantes para essa linguagem - é mais importante que sirva de contrato entre a plataforma, o compilador e o desenvolvedor. E se houver um contrato, será necessário um idioma independente do padrão de implementação específico .


Nosso primeiro requisito - recursos comuns às plataformas de destino - é expresso em uma máquina de linguagem abstrata; portanto, iniciaremos a discussão com C.


Não se trata apenas de PDP-11


A plataforma na qual a linguagem C apareceu é o PDP-11. É baseado na arquitetura tradicional de von Neumann , na qual os programas são executados seqüencialmente pelo processador central, e a memória é uma fita plana, na qual os dados e os programas são armazenados. Essa arquitetura é facilmente implementada em hardware e, com o tempo, todos os computadores de uso geral começaram a usá-la.


As melhorias modernas na arquitetura de von Neumann visam eliminar seu principal gargalo - atrasos na troca de dados entre o processador e a memória ( gargalo em inglês de von Neuman ). A diferença no desempenho da memória e da CPU levou ao surgimento de subsistemas de cache dos processadores (nível único e, posteriormente, multinível).


Mas mesmo caches nos dias de hoje não são suficientes. Os processadores modernos tornaram-se superescalares. Atrasos quando as instruções recebem dados da memória são parcialmente compensados ​​pela execução extraordinária ( paralelismo no nível das instruções) das instruções, juntamente com o preditor de ramificação .


A máquina abstrata seqüencial C (e muitas outras línguas) imita o trabalho não tanto especificamente do PDP-11, mas de qualquer computador organizado de acordo com o princípio da arquitetura de von Neumann. Inclui arquiteturas construídas em torno de processadores com um único núcleo: desktop e servidor x86, ARM móvel, provenientes da cena do Sun / Oracle SPARC e IBM POWER.


Com o tempo, vários núcleos de processamento começaram a ser integrados em um processador, como resultado, tornou-se necessário manter a coerência dos caches de cada núcleo e exigir protocolos de interação internuclear. A arquitetura de Von Neumann foi, portanto, dimensionada para vários núcleos.


A versão original da máquina abstrata C era seqüencial, não refletindo a presença de encadeamentos de execução de programa interagindo através da memória. A aparência do modelo de memória no padrão expandiu os recursos da máquina abstrata para paralelo.


Portanto, a afirmação de que a máquina C abstrata é inconsistente com a estrutura dos processadores modernos não se refere tanto a uma linguagem específica, mas a computadores que usam a arquitetura von Neumann, inclusive em execução paralela.


Mas, como profissional, quero observar o seguinte: podemos assumir que a abordagem de Fonneimann está desatualizada, podemos assumir que é relevante, mas isso não cancela o fato de que as arquiteturas de uso geral de hoje usam derivadas da abordagem tradicional.


A incorporação padronizada e portátil da arquitetura von Neumann - a máquina C abstrata - é convenientemente implementada em todas as principais plataformas e, portanto, goza de sua popularidade como montadora portátil de maneira merecedora.


Otimizando compiladores e linguagem de baixo nível


Nosso segundo requisito para um idioma de baixo nível é o acesso aos detalhes de implementação de baixo nível de cada uma das plataformas populares. No caso de C, trata-se de trabalho direto com memória e objetos, como uma matriz de bytes, a capacidade de trabalhar diretamente com endereços de bytes e aritmética avançada de ponteiros.


Os críticos de C apontam que o padrão de linguagem oferece muitas garantias em relação, por exemplo, à localização de campos individuais em estruturas e associações. Juntamente com ponteiros e mecanismos primitivos de loops, isso complica o trabalho do otimizador.


De fato, uma abordagem mais declarativa permitiria ao compilador resolver independentemente os problemas de alinhamento de dados na memória ou a ordem ideal de campos nas estruturas; e os ciclos de nível superior oferecem a liberdade que você precisa ao vetorizar.


A posição dos desenvolvedores C neste caso é a seguinte: uma linguagem de baixo nível deve permitir que ele funcione em um nível baixo o suficiente para que o programador resolva problemas de otimização de forma independente. Em C, é possível trabalhar como compilador, escolhendo, por exemplo, instruções SIMD e colocando os dados corretamente na memória.


Em outras palavras, nosso requisito de acesso aos detalhes de implementação de cada plataforma entra em conflito com os desejos dos desenvolvedores de otimizar compiladores precisamente devido à presença de ferramentas de baixo nível.


Curiosamente, em seu artigo intitulado "C não é uma linguagem de baixo nível", Lifewell argumenta paradoxalmente que C é de nível muito baixo, indicando a ausência de ferramentas de alto nível. Mas os profissionais precisam de ferramentas exatamente de baixo nível; caso contrário, a linguagem não pode ser usada para desenvolver sistemas operacionais e outros programas de baixo nível, ou seja, não atenderá ao segundo de nossos requisitos.


Distraindo-me da descrição dos problemas de otimização de C, quero observar que, no momento, não são investidos menos esforços na otimização de compiladores de linguagens de alto nível (o mesmo C # e Java) do que no GCC ou Clang. Linguagens funcionais também têm compiladores efetivos suficientes: MLTon, OCaml e outros. Mas os desenvolvedores do mesmo OCaml ainda podem apresentar desempenho, na melhor das hipóteses, com metade da velocidade do código C ...


Padrão como um bem absoluto


Em seu artigo, Chiznell cita os resultados de uma pesquisa realizada em 2015: muitos programadores cometeram erros ao resolver problemas de compreensão dos padrões C.


Suponho que um dos leitores estava lidando com o padrão C. Eu tenho uma versão em papel do C99, 900 páginas de comerciais. Esta não é uma especificação lacônica de esquema com um volume inferior a 100 páginas e nem um padrão ML lambido de 300. Prazer no trabalho ninguém recebe o padrão C: nem desenvolvedores de compiladores, nem desenvolvedores de documentos, nem programadores.


Mas devemos entender que o padrão C foi desenvolvido após o fato, após o surgimento de muitos "quase mal coloca" dialetos compatíveis. Os autores do ANSI C fizeram um ótimo trabalho resumindo as implementações existentes e cobrindo inúmeras incontáveis ​​"muletas" de imparcialidade no design da linguagem.


Pode parecer estranho que alguém se comprometa a implementar esse documento. Mas o C foi implementado por muitos compiladores. Não vou recontar as histórias de outras pessoas sobre o zoológico do mundo UNIX do final dos anos 80, especialmente porque naquela época eu mesmo não a considerava com muita confiança e só até as cinco. Mas, obviamente, todos na indústria realmente precisavam de um padrão.


O melhor é que ele existe e é implementado por pelo menos três compiladores grandes e muitos compiladores menores, que juntos suportam centenas de plataformas. Nenhuma das línguas concorrentes C, que reivindica a coroa do rei das línguas de baixo nível, pode se orgulhar de tanta diversidade e versatilidade.


Na verdade, o padrão C atual não é tão ruim. Um programador mais ou menos experiente é capaz de desenvolver um compilador C não otimizado em um período de tempo razoável, o que é confirmado pela existência de muitas implementações semi-amadoras (o mesmo TCC, LCC e 8cc).


Ter um padrão geralmente aceito significa que C satisfaz o último de nossos requisitos para um idioma de baixo nível: esse idioma é construído em uma especificação, não em uma implementação específica.


Arquiteturas alternativas - computação especial


Mas Lifewell cita outro argumento, voltando ao dispositivo dos modernos processadores de uso geral que implementam as opções de arquitetura von Neumann. Ele afirma que faz sentido mudar os princípios do processador central. Mais uma vez, essa crítica não é específica para C, mas para o modelo mais básico de programação imperativa.


De fato, existem muitas alternativas à abordagem tradicional com execução seqüencial de programas: modelos SIMD no estilo GPU, modelos no estilo de uma máquina Erlang abstrata e outros. Mas cada uma dessas abordagens tem aplicabilidade limitada quando usada em um processador central.


As GPUs, por exemplo, multiplicam notavelmente matrizes em jogos e aprendizado de máquina, mas são difíceis de usar para o traçado de raios. Em outras palavras, esse modelo é adequado para aceleradores especializados, mas não funciona para processadores de uso geral.


Erlang funciona muito bem em um cluster, mas é difícil fazer uma classificação rápida eficiente ou uma tabela de hash rápida. O modelo de atores independentes é melhor usado em um nível superior, em um cluster grande, onde cada nó ainda é a mesma máquina de alto desempenho que um processador tradicional.


Enquanto isso, os processadores modernos compatíveis com x86 há muito incluem um conjunto de instruções vetoriais semelhantes à GPU nos propósitos e nos princípios operacionais, mas preservam o circuito geral do processador no estilo von Neumann como um todo. Não tenho dúvidas de que qualquer abordagem bastante geral da computação será incluída nos processadores populares.


Existe uma opinião tão autoritária : o futuro está em aceleradores programáveis ​​especializados. Sob essas peças extraordinárias de ferro, realmente faz sentido desenvolver linguagens com semântica especial. Mas um computador de uso geral era e permanece semelhante ao próprio PDP-11, para o qual as linguagens imperativas do tipo C são tão adequadas.


C viverá


Há uma contradição fundamental no artigo de Chiznell. Ele escreve que, para garantir a velocidade dos programas em C, os processadores imitam a máquina C abstrata (e o há muito esquecido PDP-11), após o qual apontam as limitações dessa máquina. Mas não entendo por que isso significa que "C não é uma linguagem de baixo nível".


Em geral, não se trata das falhas de C como linguagem, mas de críticas às arquiteturas comuns do estilo von Neumann e ao modelo de programação que delas se segue. Mas até agora não parece que a indústria esteja pronta para abandonar a arquitetura familiar (pelo menos não nos processadores de uso geral).


Apesar da disponibilidade de muitos processadores especializados, como GPUs e TPUs, a arquitetura von Neumann está atualmente sob controle e o setor precisa de uma linguagem que permita operar no nível mais baixo possível dentro da estrutura da arquitetura mais popular. Um bastante simples, portado para dezenas de plataformas e linguagem de programação padronizada é o C (e sua família imediata).


Por tudo isso, C tem insuficiências suficientes: uma biblioteca arcaica de funções, um padrão intrincado e inconsistente e erros grosseiros de design. Mas, aparentemente, os criadores da linguagem ainda fizeram algo certo.


De uma forma ou de outra, ainda precisamos de uma linguagem de baixo nível, e ela foi criada especificamente para computadores Fonneimann populares. E deixe C desatualizado, mas, aparentemente, qualquer sucessor ainda terá que se basear nos mesmos princípios.

Source: https://habr.com/ru/post/pt465429/


All Articles