Quais técnicas podem ser usadas para acelerar os tempos de compilação do C ++?

249

Quais técnicas podem ser usadas para acelerar os tempos de compilação do C ++?

Esta questão surgiu em alguns comentários à questão Stack Overflow estilo de programação C ++ da, e estou interessado em saber quais são as idéias.

Eu já vi uma pergunta relacionada: por que a compilação C ++ demora tanto? , mas isso não fornece muitas soluções.

Scott Langham
fonte
1
Você poderia nos dar algum contexto? Ou você está procurando respostas muito gerais?
Pyrolistical
1
Muito semelhante a esta pergunta: stackoverflow.com/questions/364240/…
Adam Rosenfield
Respostas gerais. Eu tenho uma base de código muito grande escrita por muitas pessoas. Idéias sobre como atacar isso seria bom. E também, sugestões para manter as compilações rápidas para códigos recém-escritos seriam interessantes.
Scott Langham
Observe que muitas vezes uma parte relevante do tempo de compilação não é usado pelo compilador mas pelos scripts de construção
thi gg
1
Percorri esta página e não vi menções a medidas. Escrevi um pequeno script de shell que adiciona um carimbo de data / hora a cada linha de entrada que recebe, para que eu possa inserir a chamada 'make'. Isso permite ver quais destinos são os mais caros, o tempo total de compilação ou link etc. apenas comparando os carimbos de data e hora. Se você tentar essa abordagem, lembre-se de que os carimbos de data e hora serão imprecisos para compilações paralelas.
John P

Respostas:

257

Técnicas de linguagem

Pimpl Idiom

Dê uma olhada no idioma Pimpl aqui e aqui , também conhecido como ponteiro opaco ou manipular classes. Além de acelerar a compilação, também aumenta a segurança das exceções quando combinada com uma troca sem arremesso função de . O idioma do Pimpl permite reduzir as dependências entre os cabeçalhos e reduz a quantidade de recompilação que precisa ser feita.

Declarações futuras

Sempre que possível, use declarações avançadas . Se o compilador precisar apenas saber queSomeIdentifier é uma estrutura, um ponteiro ou o que for, não inclua a definição inteira, forçando o compilador a fazer mais trabalho do que o necessário. Isso pode ter um efeito em cascata, tornando esse caminho mais lento do que o necessário.

Os fluxos de E / S são particularmente conhecidos por diminuir a velocidade das construções. Se você precisar deles em um arquivo de cabeçalho, tente #including em <iosfwd>vez de <iostream>e #include o <iostream>cabeçalho somente no arquivo de implementação. o<iosfwd> cabeçalho contém apenas declarações de encaminhamento. Infelizmente, os outros cabeçalhos padrão não têm um cabeçalho de declaração respectivo.

Prefira passagem por referência a passagem por valor nas assinaturas de função. Isso eliminará a necessidade de # incluir as respectivas definições de tipo no arquivo de cabeçalho e você só precisará declarar o tipo adiante. Obviamente, prefira referências const a referências não const para evitar bugs obscuros, mas isso é um problema para outra pergunta.

Condições de guarda

Use condições de guarda para impedir que os arquivos de cabeçalho sejam incluídos mais de uma vez em uma única unidade de tradução.

#pragma once
#ifndef filename_h
#define filename_h

// Header declarations / definitions

#endif

Ao usar o pragma e o ifndef, você obtém a portabilidade da solução macro simples, bem como a otimização da velocidade de compilação que alguns compiladores podem fazer na presença do pragma once diretiva.

Reduzir a interdependência

Quanto mais modular e menos interdependente seu design de código for, em geral, menos você precisará recompilar tudo. Você também pode acabar reduzindo a quantidade de trabalho que o compilador deve executar em qualquer bloco individual ao mesmo tempo, devido ao fato de ter menos para acompanhar.

Opções do compilador

Cabeçalhos pré-compilados

Eles são usados ​​para compilar uma seção comum dos cabeçalhos incluídos uma vez para muitas unidades de tradução. O compilador o compila uma vez e salva seu estado interno. Esse estado pode ser carregado rapidamente para começar a compilar outro arquivo com o mesmo conjunto de cabeçalhos.

Tenha cuidado para incluir apenas itens raramente alterados nos cabeçalhos pré-compilados, ou você poderá fazer reconstruções completas com mais frequência do que o necessário. Este é um bom lugar para STL cabeçalhos e outros arquivos de inclusão da biblioteca.

O ccache é outro utilitário que tira proveito das técnicas de cache para acelerar as coisas.

Usar paralelismo

Muitos compiladores / IDEs suportam o uso de vários núcleos / CPUs para fazer a compilação simultaneamente. No GNU Make (geralmente usado com o GCC), use a -j [N]opção No Visual Studio, há uma opção em preferências para permitir a criação de vários projetos em paralelo. Você também pode usar a /MPopção para paralelismo em nível de arquivo, em vez de apenas paralelismo em nível de projeto.

Outros utilitários paralelos:

Use um nível mais baixo de otimização

Quanto mais o compilador tenta otimizar, mais difícil ele tem que trabalhar.

Bibliotecas compartilhadas

Mover o código modificado com menos frequência para as bibliotecas pode reduzir o tempo de compilação. Usando bibliotecas compartilhadas ( .soou .dll), você também pode reduzir o tempo de vinculação.

Obtenha um computador mais rápido

Mais memória RAM, discos rígidos mais rápidos (incluindo SSDs) e mais CPUs / núcleos farão a diferença na velocidade de compilação.

Eclipse
fonte
11
Cabeçalhos pré-compilados não são perfeitos. Um efeito colateral de usá-los é que você inclui mais arquivos do que o necessário (porque cada unidade de compilação usa o mesmo cabeçalho pré-compilado), o que pode forçar recompilações completas com mais frequência do que o necessário. Apenas algo para ter em mente.
jalf
8
Nos compiladores modernos, o #ifndef é tão rápido quanto o #pragma uma vez (desde que o protetor de inclusão esteja na parte superior do arquivo). Portanto, não há nenhum benefício para #pragma once em termos de compilação velocidades
jalf
7
Mesmo se você tiver apenas o VS 2005, e não 2008, poderá adicionar a opção / MP nas opções de compilação para habilitar a construção paralela no nível .cpp.
macbirdie
6
Os SSDs eram proibitivamente caros quando essa resposta foi escrita, mas hoje eles são a melhor opção ao compilar C ++. Você acessa muitos arquivos pequenos ao compilar; Isso requer muito IOPS, que os SSDs entregam.
MSalters
14
Prefira passagem por referência a passagem por valor nas assinaturas de função. Isso eliminará a necessidade de # incluir as respectivas definições de tipo no arquivo de cabeçalho. Isso está errado , você não precisa ter o tipo completo para declarar uma função que passa por valor; você só precisa do tipo completo para implementar ou usar essa função , mas na maioria dos casos (a menos que você esteja apenas encaminhando chamadas), você precisará dessa definição de qualquer maneira.
David Rodríguez - dribeas
43

Eu trabalho no projeto STAPL, que é uma biblioteca C ++ com muitos modelos. De vez em quando, temos que revisitar todas as técnicas para reduzir o tempo de compilação. Aqui, resumi as técnicas que usamos. Algumas dessas técnicas já estão listadas acima:

Localizando as seções que consomem mais tempo

Embora não haja correlação comprovada entre a duração dos símbolos e o tempo de compilação, observamos que tamanhos médios menores de símbolos podem melhorar o tempo de compilação em todos os compiladores. Então, seu primeiro objetivo é encontrar os maiores símbolos em seu código.

Método 1 - Classificar símbolos com base no tamanho

Você pode usar o nmcomando para listar os símbolos com base em seus tamanhos:

nm --print-size --size-sort --radix=d YOUR_BINARY

Neste comando, --radix=dpermite ver os tamanhos em números decimais (o padrão é hexadecimal). Agora, observando o símbolo maior, identifique se você pode quebrar a classe correspondente e tente reprojetá-la, fatorando as partes não modeladas em uma classe base ou dividindo a classe em várias classes.

Método 2 - Classificar símbolos com base no comprimento

Você pode executar o nmcomando regular e direcioná-lo ao seu script favorito ( AWK , Python etc.) para classificar os símbolos com base no comprimento . Com base em nossa experiência, esse método identifica o maior problema para tornar os candidatos melhores que o método 1.

Método 3 - Usar Templight

"O Templight é uma ferramenta baseada em Clang para analisar o tempo e o consumo de memória das instanciações de modelos e executar sessões de depuração interativas para obter introspecção no processo de instanciação de modelos".

Você pode instalar o Templight consultando o LLVM e o Clang ( instruções ) e aplicando o patch do Templight. A configuração padrão para LLVM e Clang está na depuração e asserções, e elas podem afetar significativamente o tempo de compilação. Parece que o Templight precisa de ambos, então você precisa usar as configurações padrão. O processo de instalação do LLVM e do Clang deve demorar cerca de uma hora.

Depois de aplicar o patch, você pode usar templight++localizado na pasta de construção especificada na instalação para compilar seu código.

Verifique se templight++está no seu PATH. Agora, para compilar, adicione as seguintes opções CXXFLAGSno seu Makefile ou nas opções da linha de comando:

CXXFLAGS+=-Xtemplight -profiler -Xtemplight -memory -Xtemplight -ignore-system

Ou

templight++ -Xtemplight -profiler -Xtemplight -memory -Xtemplight -ignore-system

Após a compilação, você terá um .trace.memory.pbf e .trace.pbf gerados na mesma pasta. Para visualizar esses rastreamentos, você pode usar as Ferramentas Templight que podem convertê-las para outros formatos. Siga estas instruções para instalar o templight-convert. Geralmente usamos a saída do callgrind. Você também pode usar a saída do GraphViz se o seu projeto for pequeno:

$ templight-convert --format callgrind YOUR_BINARY --output YOUR_BINARY.trace

$ templight-convert --format graphviz YOUR_BINARY --output YOUR_BINARY.dot

O arquivo de callgrind gerado pode ser aberto usando o kcachegrind, no qual é possível rastrear a instanciação que consome mais tempo / memória.

Reduzindo o número de instanciações de modelo

Embora não exista uma solução exata para reduzir o número de instanciações de modelos, existem algumas diretrizes que podem ajudar:

Refatorar classes com mais de um argumento de modelo

Por exemplo, se você tem uma classe,

template <typename T, typename U>
struct foo { };

e ambos Te Upode ter 10 opções diferentes, você tem aumentado as possíveis instâncias de modelos desta classe a 100. Uma maneira de resolver este é abstrair a parte comum do código para uma classe diferente. O outro método é usar a inversão de herança (revertendo a hierarquia de classes), mas certifique-se de que seus objetivos de design não sejam comprometidos antes de usar esta técnica.

Refatorar código não modelo para unidades de tradução individuais

Usando esta técnica, você pode compilar a seção comum uma vez e vinculá-la às suas outras TUs (unidades de tradução) posteriormente.

Use instanciações de modelo externo (desde C ++ 11)

Se você conhece todas as instâncias possíveis de uma classe, pode usar esta técnica para compilar todos os casos em uma unidade de tradução diferente.

Por exemplo, em:

enum class PossibleChoices = {Option1, Option2, Option3}

template <PossibleChoices pc>
struct foo { };

Sabemos que esta classe pode ter três instâncias possíveis:

template class foo<PossibleChoices::Option1>;
template class foo<PossibleChoices::Option2>;
template class foo<PossibleChoices::Option3>;

Coloque o acima em uma unidade de tradução e use a palavra-chave extern no seu arquivo de cabeçalho, abaixo da definição da classe:

extern template class foo<PossibleChoices::Option1>;
extern template class foo<PossibleChoices::Option2>;
extern template class foo<PossibleChoices::Option3>;

Essa técnica pode economizar seu tempo se você estiver compilando testes diferentes com um conjunto comum de instanciações.

NOTA: MPICH2 ignora a instanciação explícita neste momento e sempre compila as classes instanciadas em todas as unidades de compilação.

Use construções de unidade

A idéia por trás da criação da unidade é incluir todos os arquivos .cc que você usa em um arquivo e compilar esse arquivo apenas uma vez. Usando esse método, você pode evitar o restabelecimento de seções comuns de arquivos diferentes e, se o seu projeto incluir muitos arquivos comuns, você provavelmente salvará também os acessos ao disco.

Como exemplo, vamos supor que você tem três arquivos foo1.cc, foo2.cc, foo3.cce todos eles incluem tuplede STL . Você pode criar um foo-all.ccque se parece com:

#include "foo1.cc"
#include "foo2.cc"
#include "foo3.cc"

Você compila esse arquivo apenas uma vez e potencialmente reduz as instâncias comuns entre os três arquivos. Geralmente, é difícil prever se a melhoria pode ser significativa ou não. Mas um fato evidente é que você perderia o paralelismo em suas compilações (não é mais possível compilar os três arquivos ao mesmo tempo).

Além disso, se algum desses arquivos consumir muita memória, você poderá ficar sem memória antes que a compilação termine. Em alguns compiladores, como o GCC , isso pode causar ICE (erro interno do compilador) por falta de memória. Portanto, não use essa técnica, a menos que conheça todos os prós e contras.

Cabeçalhos pré-compilados

Cabeçalhos pré-compilados (PCHs) podem economizar muito tempo na compilação, compilando os arquivos de cabeçalho em uma representação intermediária reconhecível por um compilador. Para gerar arquivos de cabeçalho pré-compilados, você só precisa compilar seu arquivo de cabeçalho com o comando de compilação regular. Por exemplo, no GCC:

$ g++ YOUR_HEADER.hpp

Isso irá gerar uma YOUR_HEADER.hpp.gch file( .gché a extensão para arquivos PCH no GCC) na mesma pasta. Isso significa que, se você incluir YOUR_HEADER.hppem algum outro arquivo, o compilador utilizará o seu e YOUR_HEADER.hpp.gchnão YOUR_HEADER.hppna mesma pasta antes.

Há dois problemas com essa técnica:

  1. Você precisa garantir que os arquivos de cabeçalho pré-compilados estejam estáveis ​​e não sejam alterados ( você sempre pode alterar seu makefile )
  2. Você pode incluir apenas um PCH por unidade de compilação (na maioria dos compiladores). Isso significa que, se você tiver mais de um arquivo de cabeçalho a ser pré-compilado, precisará incluí-los em um arquivo (por exemplo, all-my-headers.hpp). Mas isso significa que você deve incluir o novo arquivo em todos os lugares. Felizmente, o GCC tem uma solução para esse problema. Use -includee forneça o novo arquivo de cabeçalho. Você pode vírgula separar arquivos diferentes usando essa técnica.

Por exemplo:

g++ foo.cc -include all-my-headers.hpp

Use namespaces não nomeados ou anônimos

Os namespaces sem nome (também conhecidos como namespaces anônimos) podem reduzir significativamente os tamanhos binários gerados. Os espaços para nome sem nome usam ligação interna, o que significa que os símbolos gerados nesses espaços para nome não serão visíveis para outras TU (unidades de tradução ou compilação). Os compiladores geralmente geram nomes exclusivos para espaços de nome sem nome. Isso significa que se você tiver um arquivo foo.hpp:

namespace {

template <typename T>
struct foo { };
} // Anonymous namespace
using A = foo<int>;

E você inclui esse arquivo em duas TUs (dois arquivos .cc e os compila separadamente). As duas instâncias de modelo foo não serão as mesmas. Isso viola a regra de definição única (ODR). Pelo mesmo motivo, o uso de espaços para nome não nomeados é desencorajado nos arquivos de cabeçalho. Sinta-se à vontade para usá-los em seus .ccarquivos para evitar que símbolos apareçam em seus arquivos binários. Em alguns casos, alterar todos os detalhes internos de um .ccarquivo mostrou uma redução de 10% nos tamanhos binários gerados.

Alterando as opções de visibilidade

Nos compiladores mais novos, você pode selecionar seus símbolos para serem visíveis ou invisíveis nos DSOs (Dynamic Shared Objects). Idealmente, alterar a visibilidade pode melhorar o desempenho do compilador, otimizações de tempo de link (LTOs) e tamanhos binários gerados. Se você olhar para os arquivos de cabeçalho STL no GCC, poderá ver que ele é amplamente usado. Para habilitar as opções de visibilidade, você precisa alterar seu código por função, por classe, por variável e, mais importante, por compilador.

Com a ajuda da visibilidade, você pode ocultar os símbolos que os considera privados dos objetos compartilhados gerados. No GCC, você pode controlar a visibilidade dos símbolos passando padrão ou oculto para a -visibilityopção do seu compilador. Em certo sentido, isso é semelhante ao namespace sem nome, mas de uma maneira mais elaborada e intrusiva.

Se você desejar especificar as visibilidades por caso, precisará adicionar os seguintes atributos às suas funções, variáveis ​​e classes:

__attribute__((visibility("default"))) void  foo1() { }
__attribute__((visibility("hidden")))  void  foo2() { }
__attribute__((visibility("hidden")))  class foo3   { };
void foo4() { }

A visibilidade padrão no GCC é padrão (pública), o que significa que se você compilar acima como um -sharedmétodo de biblioteca compartilhada ( ), foo2e a classe foo3não será visível em outras TUs ( foo1e foo4estará visível). Se você compilar com -visibility=hidden, somente foo1será visível. Mesmo foo4estaria escondido.

Você pode ler mais sobre visibilidade no wiki do GCC .

Mani Zandifar
fonte
33

Eu recomendo estes artigos em "Jogos de dentro, design e programação independentes de jogos":

É verdade que eles são muito antigos - você terá que testar novamente tudo com as versões mais recentes (ou versões disponíveis para você), para obter resultados realistas. De qualquer forma, é uma boa fonte de idéias.

Paulius
fonte
17

Uma técnica que funcionou muito bem para mim no passado: não compile vários arquivos de origem C ++ independentemente, mas gere um arquivo C ++ que inclua todos os outros arquivos, como este:

// myproject_all.cpp
// Automatically generated file - don't edit this by hand!
#include "main.cpp"
#include "mainwindow.cpp"
#include "filterdialog.cpp"
#include "database.cpp"

Obviamente, isso significa que você precisa recompilar todo o código-fonte incluído, caso alguma das fontes mude, para que a árvore de dependência piore. No entanto, compilar vários arquivos de origem como uma unidade de tradução é mais rápido (pelo menos nos meus experimentos com MSVC e GCC) e gera binários menores. Eu também suspeito que o compilador tenha mais potencial para otimizações (já que ele pode ver mais código de uma vez).

Essa técnica quebra em vários casos; por exemplo, o compilador será resgatado caso dois ou mais arquivos de origem declarem uma função global com o mesmo nome. Não consegui encontrar essa técnica descrita em nenhuma das outras respostas, por isso estou mencionando aqui.

Pelo que vale a pena, o Projeto KDE usou exatamente a mesma técnica desde 1999 para criar binários otimizados (possivelmente para uma versão). A mudança para o script de configuração da construção foi chamada --enable-final. Por interesse arqueológico, descobri a postagem que anunciava esse recurso: http://lists.kde.org/?l=kde-devel&m=92722836009368&w=2

Frerich Raabe
fonte
2
Não tenho certeza se é realmente a mesma coisa, mas acho que ativar "Otimização de programa inteiro" no VC ++ ( msdn.microsoft.com/en-us/library/0zza0de8%28VS.71%29.aspx ) deve ter o mesmo efeito no desempenho em tempo de execução do que o que você está sugerindo. O tempo de compilação, no entanto, pode definitivamente ser melhor em sua abordagem!
Philipp
1
@ Frederich: Você está descrevendo as compilações do Unity mencionadas na resposta do OJ. Eu também os vi chamados builds em massa e master builds.
Idbrii 19/03/12
Então, como um UB se compara ao WPO / LTCG?
paulm 28/01
Isso é potencialmente útil apenas para compilações únicas, não durante o desenvolvimento, onde você alterna entre edição, construção e teste. No mundo moderno, quatro núcleos é a norma, talvez alguns anos depois a contagem de núcleos seja significativamente maior. Se o compilador e o vinculador não puderem utilizar vários encadeamentos, talvez a lista de arquivos possa ser dividida em <core-count> + Nsublistas que são compiladas paralelamente onde Nhá algum número inteiro adequado (dependendo da memória do sistema e de que outra forma a máquina é usada).
precisa saber é
15

Há um livro inteiro sobre esse tópico, intitulado Design de software C ++ em larga escala (escrito por John Lakos).

Os modelos pré-datam o livro, portanto, ao conteúdo desse livro, adicione "o uso de modelos também pode tornar o compilador mais lento".

ChrisW
fonte
O livro é frequentemente mencionado nesses tipos de tópicos, mas para mim era escasso em informações. Ele basicamente declara usar declarações avançadas o máximo possível e desacoplar dependências. Isso indica um pouco o óbvio, além do fato de que o uso do idioma pimpl tem desvantagens em tempo de execução.
gast128
@ gast128 Acho que seu objetivo é usar idiomas de codificação que permitam uma recompilação incremental, ou seja, para que, se você mudar um pouco de fonte em algum lugar, não precise recompilar tudo.
ChrisW
15

Vou apenas vincular a minha outra resposta: Como você reduz o tempo de compilação e o tempo de vinculação para projetos do Visual C ++ (C ++ nativo)? . Outro ponto que quero acrescentar, mas que costuma causar problemas é usar cabeçalhos pré-compilados. Mas, por favor, use-os apenas para peças que quase nunca mudam (como cabeçalhos de kit de ferramentas da GUI). Caso contrário, eles custarão mais tempo do que economizam no final.

Outra opção é, quando você trabalha com o GNU make, ativar a -j<N>opção:

  -j [N], --jobs[=N]          Allow N jobs at once; infinite jobs with no arg.

Eu costumo tê-lo 3desde que eu tenho um dual core aqui. Ele executará os compiladores em paralelo para diferentes unidades de conversão, desde que não haja dependências entre eles. A vinculação não pode ser feita em paralelo, pois existe apenas um processo de vinculador que vincula todos os arquivos de objeto.

Mas o vinculador em si pode ser encadeado, e é isso que o vinculador ELF faz. É um código C ++ encadeado otimizado que, diz-se, vincula os arquivos de objeto ELF uma magnitude mais rápido que o antigo (e foi realmente incluído nos binutils ).GNU gold ld

Johannes Schaub - litb
fonte
Ok sim. Desculpe, essa pergunta não surgiu quando eu procurei.
Scott Langham
você não precisava se desculpar. isso foi para o Visual C ++. sua pergunta parece ser para qualquer compilador. então tudo bem :)
Johannes Schaub - litb
12

Aqui estão alguns:

  • Use todos os núcleos do processador iniciando um trabalho de compilação múltipla ( make -j2é um bom exemplo).
  • Desligar ou reduzir optimizações (por exemplo, GCC é muito mais rápida com -O1que -O2ou -O3).
  • Use cabeçalhos pré-compilados .
Milan Babuškov
fonte
12
Para sua informação, acho que geralmente é mais rápido iniciar mais processos do que núcleos. Por exemplo, em um sistema quad core, geralmente uso -j8, não -j4. A razão para isso é que, quando um processo é bloqueado na E / S, o outro pode estar compilando.
Mr Fooz
@ MrFooz: Eu testei isso há alguns anos, compilando o kernel do Linux (do armazenamento em RAM) em um i7-2700k (4 núcleos, 8 threads, defino um multiplicador constante). Eu esqueço o melhor resultado exato, mas -j12ao redor -j18foram consideravelmente mais rápidos do que -j8, como você sugere. Eu estou querendo saber quantos núcleos que você pode ter antes de banda de memória torna-se o fator limitante ...
Mark K Cowan
@ MarkKCowan depende de muitos fatores. Computadores diferentes têm larguras de banda de memória totalmente diferentes. Atualmente, com os processadores de ponta, são necessários vários núcleos para saturar o barramento de memória. Além disso, existe o equilíbrio entre E / S e CPU. Algum código é muito fácil de compilar, outro código pode ser lento (por exemplo, com muitos modelos). Minha regra atual é -jcom o dobro do número de núcleos reais.
Mr Fooz
11

Depois de aplicar todos os truques de código acima (declarações avançadas, reduzindo a inclusão de cabeçalhos ao mínimo em cabeçalhos públicos, forçando a maioria dos detalhes dentro do arquivo de implementação com o Pimpl ...) e nada mais pode ser obtido no idioma, considere seu sistema de compilação . Se você usa Linux, considere o uso de distcc (compilador distribuído) e ccache (compilador de cache).

O primeiro, distcc, executa a etapa do pré-processador localmente e envia a saída para o primeiro compilador disponível na rede. Requer as mesmas versões do compilador e da biblioteca em todos os nós configurados na rede.

O último, ccache, é um cache do compilador. Ele executa novamente o pré-processador e, em seguida, verifica com um banco de dados interno (mantido em um diretório local) se esse arquivo de pré-processador já foi compilado com os mesmos parâmetros do compilador. Caso isso aconteça, ele apenas exibe o binário e a saída da primeira execução do compilador.

Ambos podem ser usados ​​ao mesmo tempo, para que, se o ccache não tiver uma cópia local, ele possa enviá-lo através da rede para outro nó com distcc, ou apenas injetar a solução sem processamento adicional.

David Rodríguez - dribeas
fonte
2
Eu não acho que o distcc exija as mesmas versões de biblioteca em todos os nós configurados. O distcc faz apenas a compilação remotamente, não o link. Ele também envia o código pré - processado pela conexão, para que os cabeçalhos disponíveis no sistema remoto não importem.
Frerich Raabe #
9

Quando saí da faculdade, o primeiro código C ++ real e digno de produção que eu tinha tinha essas diretivas arcanas #ifndef ... #endif entre elas, onde os cabeçalhos eram definidos. Perguntei ao cara que estava escrevendo o código sobre essas coisas abrangentes de uma maneira muito ingênua e fui apresentado ao mundo da programação em larga escala.

Voltando ao assunto, usar diretivas para impedir definições duplicadas de cabeçalho foi a primeira coisa que aprendi quando se trata de reduzir o tempo de compilação.

questzen
fonte
1
velho mas bom. às vezes o óbvio é esquecido.
alcor
1
'inclua guardas'
gast128 28/08/19
8

Mais RAM.

Alguém falou sobre unidades de RAM em outra resposta. Eu fiz isso com um 80286 e Turbo C ++ (mostra a idade) e os resultados foram fenomenais. Como foi a perda de dados quando a máquina travou.

senhor calendário
fonte
no DOS, você não pode ter muita memória embora
phuclv
6

Use declarações avançadas sempre que puder. Se uma declaração de classe usa apenas um ponteiro ou referência a um tipo, você pode simplesmente declará-la e incluir o cabeçalho do tipo no arquivo de implementação.

Por exemplo:

// T.h
class Class2; // Forward declaration

class T {
public:
    void doSomething(Class2 &c2);
private:
    Class2 *m_Class2Ptr;
};

// T.cpp
#include "Class2.h"
void Class2::doSomething(Class2 &c2) {
    // Whatever you want here
}

Menos inclusões significa muito menos trabalho para o pré-processador, se você fizer o suficiente.

Evan Teran
fonte
Isso não importa apenas quando o mesmo cabeçalho é incluído em várias unidades de tradução? Se houver apenas uma unidade de tradução (como costuma ser o caso quando modelos são usados), isso parece não ter impacto.
AlwaysLearning
1
Se houver apenas uma unidade de tradução, por que se incomodar em colocá-la em um cabeçalho? Não faria mais sentido apenas colocar o conteúdo no arquivo de origem? O ponto principal dos cabeçalhos não é que ele provavelmente será incluído em mais de um arquivo de origem?
Evan Teran
5

Usar

#pragma once

na parte superior dos arquivos de cabeçalho, portanto, se eles forem incluídos mais de uma vez em uma unidade de tradução, o texto do cabeçalho será incluído e analisado apenas uma vez.

Scott Langham
fonte
2
Embora amplamente suportado, o #pragma já não é padrão. Veja en.wikipedia.org/wiki/Pragma_once
ChrisInEdmonton
7
E hoje em dia, guardas regulares incluem o mesmo efeito. Contanto que eles estejam no topo do arquivo, o compilador é totalmente capaz de tratá-los como #pragma uma vez
jalf
5

Apenas para completar: uma compilação pode ser lenta porque o sistema de compilação está sendo estúpido e também porque o compilador está demorando muito tempo para fazer seu trabalho.

Leia Recursive Make Considered Nocivo (PDF) para uma discussão sobre este tópico em ambientes Unix.

dmckee --- gatinho ex-moderador
fonte
4
  • Atualize seu computador

    1. Obtenha um quad core (ou um sistema quad-quad)
    2. Obtenha MUITA RAM.
    3. Use uma unidade de RAM para reduzir drasticamente os atrasos de E / S do arquivo. (Existem empresas que fabricam unidades IDE e SATA RAM que funcionam como discos rígidos).
  • Então você tem todas as suas outras sugestões típicas

    1. Use cabeçalhos pré-compilados, se disponíveis.
    2. Reduza a quantidade de acoplamento entre partes do seu projeto. Alterar um arquivo de cabeçalho geralmente não deve exigir a recompilação de todo o projeto.
Uhall
fonte
4

Eu tive uma idéia sobre o uso de uma unidade de RAM . Descobriu-se que, para os meus projetos, não faz muita diferença, afinal. Mas eles ainda são bem pequenos. Tente! Eu estaria interessado em ouvir o quanto isso ajudou.

Vilx-
fonte
Hã. Por que alguém votou negativamente nisso? Eu vou tentar amanhã.
Scott Langham
1
Espero que o voto negativo seja porque nunca faz uma grande diferença. Se você tiver RAM não utilizada suficiente, o sistema operacional o usará de maneira inteligente como um cache de disco.
MSalters
1
@MSalters - e quanto seria "suficiente"? Eu sei que essa é a teoria, mas por alguma razão usando um RAMdrive não realmente dar um impulso significativo. Vá descobrir ...
Vilx-
1
o suficiente para compilar seu projeto e ainda armazenar em cache os arquivos de entrada e temporários. Obviamente, o lado em GB dependerá diretamente do tamanho do seu projeto. Deve-se notar que em sistemas operacionais mais antigos (WinXP em particular), os caches de arquivos eram bastante preguiçosos, deixando a RAM sem uso.
MSalters
Certamente o drive ram é mais rápido se os arquivos já estiverem no ram, em vez de fazer um monte de IO lento primeiro, então eles estão no ram? (repita o processo para arquivos que foram alterados - escreva-os de volta no disco, etc.)
paulm 3/13
3

A vinculação dinâmica (.so) pode ser muito mais rápida que a vinculação estática (.a). Especialmente quando você tem uma unidade de rede lenta. Isso ocorre porque você tem todo o código do arquivo .a que precisa ser processado e gravado. Além disso, um arquivo executável muito maior precisa ser gravado no disco.


fonte
ligação dinâmica prevenir muitos tipos de otimizações ligação em tempo para que a saída pode ser mais lenta em muitos casos
phuclv
3

Não sobre o tempo de compilação, mas sobre o tempo de compilação:

  • Use o ccache se precisar reconstruir os mesmos arquivos ao trabalhar em seus buildfiles

  • Use ninja-build em vez de make. Atualmente, estou compilando um projeto com ~ 100 arquivos de origem e tudo é armazenado em cache pelo ccache. faça necessidades 5 minutos, ninja menos que 1.

Você pode gerar seus arquivos ninja do cmake com -GNinja.

thi gg
fonte
3

Onde você está gastando seu tempo? Você está vinculado à CPU? Memória ligada? Disco ligado? Você pode usar mais núcleos? Mais RAM? Você precisa de RAID? Deseja simplesmente melhorar a eficiência do seu sistema atual?

Em gcc / g ++, você olhou para o ccache ? Pode ser útil se você estiver fazendo make clean; makemuito.

Mr.Ree
fonte
2

Discos rígidos mais rápidos.

Os compiladores gravam muitos arquivos (e possivelmente enormes) no disco. Trabalhe com SSD em vez do disco rígido típico e os tempos de compilação são muito menores.

linello
fonte
2

No Linux (e talvez em alguns outros * NIXes), você pode realmente acelerar a compilação NÃO OLHANDO na saída e mudando para outra TTY.

Aqui está o experimento: printf desacelera meu programa

Flavius
fonte
2

Os compartilhamentos de rede diminuirão drasticamente sua compilação, pois a latência de busca é alta. Para algo como o Boost, fez uma enorme diferença para mim, mesmo que nossa unidade de compartilhamento de rede seja bastante rápida. O tempo para compilar um programa Boost de brinquedo passou de 1 minuto para 1 segundo quando eu mudei de um compartilhamento de rede para um SSD local.

Mark Lakata
fonte
2

Se você possui um processador multicore, o Visual Studio (2005 e posterior) e o GCC suportam compilações com vários processadores. É algo para ativar se você tiver o hardware, com certeza.

Peter Mortensen
fonte
2
@Fellman, Veja algumas das outras respostas - use a opção -j #.
Strager
1

Embora não seja uma "técnica", não consegui descobrir como os projetos do Win32 com muitos arquivos de origem foram compilados mais rapidamente do que o meu projeto vazio "Hello World". Assim, espero que isso ajude alguém como ele me ajudou.

No Visual Studio, uma opção para aumentar o tempo de compilação é a vinculação incremental ( / INCREMENTAL ). É incompatível com a Geração de código em tempo de link ( / LTCG ), portanto, lembre-se de desabilitar o link incremental ao criar versões.

Nathan Goings
fonte
1
desativar a geração de código em tempo de link não é uma boa sugestão, pois desativa muitas otimizações. É necessário habilitar o /INCREMENTALno modo de depuração única
phuclv
1

A partir do Visual Studio 2017, você tem a capacidade de ter algumas métricas de compilador sobre o que leva tempo.

Inclua esses parâmetros em C / C ++ -> Linha de Comandos (Opções Adicionais) na janela de propriedades do projeto: /Bt+ /d2cgsummary /d1reportTime

Você pode ter mais informações neste post .

Xavier Bigand
fonte
0

O uso de links dinâmicos em vez de estáticos torna o compilador mais rápido possível.

Se você usar t Cmake, ative a propriedade:

set(BUILD_SHARED_LIBS ON)

Versão de compilação, o uso de links estáticos pode otimizar mais.

Cheng
fonte