Como lidar com o problema de (compilar) uma grande base de código?


10

Embora eu possa codificar, ainda não tenho nenhuma experiência em trabalhar em grandes projetos. O que eu fiz até agora foi codificar pequenos programas que são compilados em questão de segundos (vários exercícios de c / c ++ como algoritmos, princípios de programação, idéias, paradigmas ou apenas experimentar APIs ...) ou trabalhar em alguns projetos menores que estavam feito em uma (s) linguagem (s) de script (python, php, js) em que não é necessária nenhuma compilação.

O problema é que, ao codificar em uma linguagem de script, sempre que eu quero tentar se algo funciona - eu apenas executo o script e vejo o que acontece. Se as coisas não funcionarem, eu posso simplesmente mudar o código e testá-lo novamente, executando o script novamente e continuar fazendo isso até obter o resultado desejado. Meu ponto é que você não precisa esperar pelo qualquer coisa para compilar e, por isso, é muito fácil usar uma grande base de códigos, modificá-la, adicionar algo a ela ou simplesmente brincar com ela - você pode ver as alterações instantaneamente.

Como exemplo, tomarei o Wordpress. É muito fácil tentar descobrir como criar um plugin para ele. Primeiro, você começa criando um plugin simples "Hello World", depois cria uma interface simples para o painel de administração para se familiarizar com a API, depois a cria e torna algo mais complexo, enquanto isso muda a aparência de algumas vezes .. A idéia de ter que recompilar algo tão grande quanto o WP repetidamente, após cada pequena alteração, para tentar "se funciona" e "como funciona / sente", parece ineficiente, lenta e errada.

Agora, como eu poderia fazer isso com um projeto escrito em uma linguagem compilada? Eu gostaria de contribuir com alguns projetos de código aberto e essa pergunta continua me incomodando. A situação provavelmente difere de projeto para projeto, onde alguns deles que foram pensados ​​com sabedoria serão "modulares" de alguma forma, enquanto outros serão apenas um grande blob que precisa ser recompilado repetidamente.

Gostaria de saber mais sobre como isso é feito corretamente. Quais são algumas práticas comuns, abordagens e desenhos de projetos (padrões?) Para lidar com isso? Como essa "modularidade" é chamada no mundo dos programadores e para que devo pesquisar no Google para saber mais sobre isso? É frequente os projetos crescerem de suas proporções de primeiro pensamento que se tornam problemáticas depois de um tempo? Existe alguma maneira de evitar a longa compilação de projetos não tão bem projetados? Uma maneira de modulá-los de alguma forma (talvez excluindo partes não vitais do programa durante o desenvolvimento (alguma outra idéia?))?

Obrigado.


4
Ob. XKCD e a camiseta do thinkgeek relevante * 8 ')
Mark Booth

11
Se você trabalha em um projeto grande o suficiente, com um orçamento grande o suficiente você pode obter servidores construção para fazer a compilação para você :)
SoylentGray

@Chad - Eu sei disso, mas é apenas a minha casa gnu / linux máquina desktop e me no momento :)
pootzko

@Chad Ok, então você está nos dizendo que precisamos de servidores dedicados para lidar com o volume de Java (ou qualquer outra linguagem compilada)? Isso é total porcaria #
Kolob Canyon

11
@KolobCanyon - Não, eu estou dizendo que existe uma escala na qual você possa trabalhar que exigiria. e que eles são baratos o suficiente agora que ter uma VM sob demanda dedicada à compilação e autmação rápidas de testes é fácil o suficiente para que a escala não seja tão grande.
precisa saber é o seguinte

Respostas:


8

Assim como foi dito, você nunca recompila todo o projeto cada vez que faz uma pequena alteração. Em vez disso, você apenas recompila a parte do código que foi alterada, bem como todo o código, dependendo dela.

No C / C ++, a compilação é bastante direta. Você compila a conversão de cada arquivo de origem em código de máquina (nós os chamamos de arquivos de objeto * .o) e depois vincula todos os seus arquivos de objeto em um grande executável.

Assim como o MainMa mencionou, algumas bibliotecas são construídas em arquivos separados, que serão vinculados dinamicamente em tempo de execução ao executável. Essas bibliotecas são chamadas de Objetos Compartilhados (* .so) no Unix e nas Bibliotecas Dinamicamente Vinculadas (DLL) no Windows. As bibliotecas dinâmicas têm muitas vantagens, uma das quais você não precisa compilá-las / vinculá-las, a menos que seu código-fonte mude efetivamente.

Existem ferramentas de automação de construção que ajudam você a:

  • Especifique dependências entre diferentes partes da sua árvore de origem.
  • Inicie compilações pontuais e discretas apenas na parte que foi modificada.

Os mais famosos (make, ant, maven, ...) podem detectar automaticamente quais partes do código foram alteradas desde a última compilação e exatamente qual objeto / binário precisa ser atualizado.

No entanto, esse é o custo (relativamente pequeno) de ter que escrever um "script de construção". É um arquivo que contém todas as informações sobre sua compilação, como definir os destinos e suas dependências, definir qual compilador você deseja e quais opções usar, definir seu ambiente de compilação, seus caminhos de biblioteca, ... Você talvez tenha ouvido falar sobre Makefiles (muito comum no mundo Unix) ou build.xml (muito popular no mundo Java). Isto é o que eles fazem.


2
Ant (Java) não é capaz de determinar o que precisa ser recompilado. Ele lida com a parte trivial do trabalho, recompilando o código-fonte alterado, mas não entende as dependências de classe. Contamos com IDEs para isso, e eles darão errado se uma assinatura de método for alterada de uma maneira que não exija uma alteração no código de chamada.
Kevin Cline

@kevincline I segundo este - ANT compila tudo menos que você especifique algo diferente no build.xmlarquivo
Kolob Canyon

7

Você não recompila todo o projeto todas as vezes. Por exemplo, se for um aplicativo C / C ++, há chances de ele ser separado em bibliotecas (DLLs no Windows), cada biblioteca sendo compilada separadamente.

O projeto em si geralmente é compilado diariamente em um servidor dedicado: essas são construções noturnas. Esse processo pode demorar bastante, pois inclui não apenas o tempo de compilação, mas também o tempo gasto na execução de testes de unidade, outros testes e outros processos.


3
Se eu não recompilar tudo, então quando vou ter tempo para brincar com meu Trebuchet
SoylentGray

5

Eu acho que todas as respostas até agora foram aludidas também, é que grandes projetos de software são quase sempre divididos em partes muito menores. Cada peça é normalmente armazenada em seu próprio arquivo.

Essas peças são compiladas individualmente para criar objetos. Os objetos são então vinculados para formar o produto final. [De certa forma, é como construir coisas com Legos. Você não tenta moldar a coisa final com um grande pedaço de plástico, mas combina vários pedaços menores para fazê-lo.]

Quebrar o projeto em partes que são compiladas individualmente permite que algumas coisas legais aconteçam.

Edifício Incremental

Primeiro, quando você muda uma peça, geralmente não precisa recompilar todas as peças. De um modo geral, desde que você não altere a maneira como as outras peças interagem com a peça, as outras não precisam ser recompiladas.

Isso dá origem à ideia de construção incremental . Ao fazer uma construção incremental, apenas as partes afetadas pela alteração são recompiladas. Isso acelera bastante o tempo de desenvolvimento. É verdade que talvez você ainda precise esperar que tudo seja vinculado novamente, mas isso ainda poupa muito em ter que recompilar e vincular tudo novamente. (BTW: Alguns sistemas / idiomas oferecem suporte à vinculação incremental, de modo que apenas as coisas que foram alteradas precisam ser vinculadas novamente. O custo para isso geralmente está no desempenho e tamanho do código ruins.)

Teste de Unidade

A segunda coisa que ter peças pequenas permite fazer é testar individualmente as peças antes de serem combinadas. Isso é conhecido como teste de unidade . Nos testes de unidade, cada unidade é testada individualmente antes de ser integrada (combinada) com o restante do sistema. Os testes de unidade são normalmente escritos para que possam ser executados rapidamente sem envolver o restante do sistema.

O caso limitante da aplicação de testes é visto em Test Driven Development (TDD). Nesse modelo de desenvolvimento, nenhum código é gravado / modificado, a menos que seja para corrigir um teste com falha.

Tornando mais fácil

Portanto, dividir as coisas parece bom, mas também parece ser necessário muito trabalho para construir o projeto: você precisa descobrir quais peças foram alteradas e o que depende dessas peças, compilar cada peça e vincular tudo.

Felizmente, os programadores são preguiçosos *, então inventam muitas ferramentas para facilitar seus trabalhos. Para esse fim, muitas ferramentas foram escritas para automatizar a tarefa acima. Os mais famosos já foram mencionados (marca, formiga, maven). Essas ferramentas permitem definir quais peças precisam ser reunidas para criar seu projeto final e como as peças dependem umas das outras (ou seja, se você alterar isso, será necessário recompilar). O resultado é que emitir apenas um comando descobre o que precisa ser recompilado, compila e vincula tudo.

Mas ainda resta descobrir como as coisas se relacionam. Isso dá muito trabalho e, como eu disse antes, os programadores são preguiçosos. Então eles criaram outra classe de ferramentas. Essas ferramentas foram escritas para determinar as dependências para você! Freqüentemente, as ferramentas fazem parte de Ambientes de Desenvolvimento Integrado (IDEs), como Eclipse e Visual Studio, mas também existem alguns autônomos usados ​​para aplicativos genéricos e específicos (makedep, programas QMake for Qt).

* Na verdade, os programadores não são realmente preguiçosos, eles gostam de gastar seu tempo trabalhando em problemas, não realizando tarefas repetitivas que podem ser automatizadas por um programa.


5

Aqui está minha lista de itens que você pode tentar acelerar as compilações em C / C ++:

  • Você está configurado para reconstruir apenas o que mudou? A maioria dos ambientes faz isso por padrão. Não é necessário recompilar um arquivo se ele ou nenhum dos cabeçalhos foi alterado. Da mesma forma, não há razão para reconstruir uma DLL / exe se todos os links em objs / lib não tiverem sido alterados.
  • Coloque coisas de terceiros que nunca mudam e os cabeçalhos associados em alguma área da biblioteca de códigos somente leitura. Você só precisa dos cabeçalhos e dos binários associados. Você nunca precisará reconstruir isso a partir da fonte, exceto uma vez.
  • Ao reconstruir tudo, os dois fatores limitantes da minha experiência foram o número de núcleos e a velocidade do disco . Adquira um robusto quad core, máquina hyperthread com um disco rígido realmente bom e seu desempenho melhorará. Considere uma unidade de estado sólido - tenha em mente que as mais baratas podem ser piores que um bom disco rígido. Considere usar o ataque para aumentar o seu disco rígido
  • Use um sistema de compilação distribuído, como o Incredibuild, que dividirá a compilação em outras estações de trabalho na sua rede. (Verifique se você possui uma rede sólida).
  • Configure uma construção de unidade para evitar que você recarregue constantemente os arquivos de cabeçalho.

Na minha experiência (não muito, mas bem), a velocidade do disco começa a se tornar irrelevante se o seu projeto ultrapassar "muito pequeno". Pense no que você diz no seu próximo tópico: você está usando a rede para acelerar a compilação. Se o disco foi um grande gargalo, recorrer à rede não parece ser uma ação muito boa.
R. Martinho Fernandes

Outra solução barata é compilar em um tmpfs. Pode aumentar significativamente o desempenho se o processo de compilação estiver vinculado a E / S.
Artefact2

4

A idéia de ter que recompilar algo tão grande quanto o WP repetidamente, após cada pequena alteração para tentar "se funciona" e "como funciona / parece", parece ineficiente, lenta e errada.

Executar algo interpretado também é muito ineficiente e lento, e (sem dúvida) errado. Você está reclamando dos requisitos de tempo no PC do desenvolvedor, mas não compilar causa requisitos de tempo no PC do usuário , o que é provavelmente muito pior.

Mais importante, os sistemas modernos podem fazer reconstruções incrementais bastante avançadas e não é comum recompilar tudo para pequenas alterações - os sistemas compilados podem incluir componentes de script, especialmente comuns para coisas como a interface do usuário.


11
Acredito que minha pergunta não foi feita para ser interpretada versus compilar o debate da abordagem. Em vez disso, apenas pedi conselhos sobre como o desenvolvimento de um grande projeto (compilado) é feito corretamente. Obrigado pela ideia de reconstruções incrementais.
pootzko

@pootzko: Bem, é bastante injusto discutir as desvantagens da compilação quando você também não está falando sobre as desvantagens da interpretação.
DeadMG

11
não, não é. é outro debate e não tem nada a ver com a minha pergunta. Não estou dizendo que é algo que não deva ser discutido. deveria, mas não aqui.
pootzko

@pootzko: Então você não deve dedicar a maior parte de sua pergunta a enumerar o que você não gosta em compilar. Você deveria ter escrito algo muito mais curto e mais sucinto, como "Como os tempos de compilação de grandes projetos podem ser reduzidos?".
DeadMG

Eu não sabia que tinha que perguntar a alguém sobre como eu "deveria" fazer minha pergunta ..? : OI escrevi o que fiz para explicar melhor meu ponto de vista, para que outros pudessem entender melhor e me explicar como conseguir a mesma coisa / similar em linguagens compiladas. Mais uma vez - não pedi a ninguém que me dissesse se os idiomas interpretados causam piores requisitos de tempo no PC do usuário. Eu sei disso e não tem nada a ver com a minha pergunta - "como é feito com as linguagens compiladas", desculpe. Outras pessoas parecem ter descoberto o que eu pedi, então eu não acho que a minha pergunta não é clara o suficiente ..
pootzko

4
  • Reconstrução Parcial

Se o projeto implementar o DAG de dependência de compilação adequado, você poderá apenas recompilar os arquivos de objeto que sua alteração afeta.

  • Processo de compilação múltipla

Assumindo também um DAG de dependência de compilação adequado, você pode compilar usando vários processos. Um trabalho por núcleo / CPU é a norma.

  • Testes executáveis

Você pode criar vários executáveis ​​para teste que vinculam apenas arquivos de objetos específicos.


2

Além da resposta da MainMa, também acabamos de atualizar as máquinas em que trabalhamos. Uma das melhores compras que fizemos foi um SSD para o qual você não pode deixar de recompilar todo o projeto.

Outra sugestão seria tentar um compilador diferente. Naquela época, passamos do compilador Java para o Jikes e agora passamos a usar o compilador fornecido com o Eclipse (não sei se ele tem um nome), que tira melhor proveito dos processadores multicore.

Nosso projeto de 37.000 arquivos levou cerca de 15 minutos para compilar do zero antes de fazermos essas alterações. Após as alterações, foi reduzido para 2-3 minutos.

Claro, vale a pena mencionar o argumento da MainMa novamente. Não recompile o projeto inteiro toda vez que quiser ver uma alteração.

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.