Pergunta

Estamos obtendo tempos de compilação muito lentos, que podem levar mais de 20 minutos em máquinas dual core 2GHz e 2G Ram.

Muito disso se deve ao tamanho da nossa solução, que cresceu para mais de 70 projetos, bem como ao VSS, que é um gargalo quando você tem muitos arquivos.(trocar o VSS não é uma opção, infelizmente, então não quero que isso se transforme em uma festança do VSS)

Estamos analisando projetos de fusão.Também pretendemos ter múltiplas soluções para conseguir uma maior separação de interesses e tempos de compilação mais rápidos para cada elemento da aplicação.Posso ver que isso se tornará um inferno de DLL enquanto tentamos manter as coisas sincronizadas.

Estou interessado em saber como outras equipes lidaram com esse problema de escalonamento, o que você faz quando sua base de código atinge uma massa crítica que você perde metade do dia observando a barra de status entregar mensagens de compilação.

ATUALIZAREsqueci de mencionar que esta é uma solução C#.Obrigado por todas as sugestões de C++, mas já faz alguns anos que não me preocupo com cabeçalhos.

EDITAR:

Boas sugestões que ajudaram até agora (sem dizer que não há outras boas sugestões abaixo, apenas o que ajudou)

  • Novo laptop de 3 GHz – o poder da utilização perdida faz maravilhas quando se trata de gerenciamento
  • Desative o antivírus durante a compilação
  • 'Desconectando' do VSS (na verdade, a rede) durante a compilação - posso fazer com que removamos completamente a integração VS-VSS e continuemos usando a UI do VSS

Ainda não estou cheirando uma compilação, mas tudo ajuda.

Orion mencionou em um comentário que os genéricos também podem ter uma função.Pelos meus testes, parece haver um impacto mínimo no desempenho, mas não alto o suficiente para garantir - os tempos de compilação podem ser inconsistentes devido à atividade do disco.Devido a limitações de tempo, meus testes não incluíram tantos genéricos, ou tanto código, como apareceria no sistema ativo, então isso pode se acumular.Eu não evitaria usar genéricos onde eles deveriam ser usados, apenas para desempenho em tempo de compilação

GAMBIARRA

Estamos testando a prática de construir novas áreas do aplicativo em novas soluções, importando as DLLs mais recentes conforme necessário, integrando-as à solução maior quando estivermos satisfeitos com elas.

Também podemos fazer o mesmo com o código existente, criando soluções temporárias que apenas encapsulam as áreas nas quais precisamos trabalhar e descartando-as após reintegrar o código.Precisamos pesar o tempo que levará para reintegrar esse código em relação ao tempo que ganhamos por não ter experiências como Rip Van Winkle com recompilação rápida durante o desenvolvimento.

Foi útil?

Solução

A equipe do Chromium.org listou diversas opções para acelerando a construção (neste ponto, na metade da página):

Em ordem decrescente de aceleração:

  • Instale o hotfix da Microsoft 935225.
  • Instale o hotfix da Microsoft 947315.
  • Use um verdadeiro processador multicore (ou seja,um Intel Core Duo 2;não um Pentium 4 HT).
  • Use 3 compilações paralelas.No Visual Studio 2005, você encontrará a opção em Ferramentas > Opções...> Projetos e soluções > Construir e executar > número máximo de compilações de projetos paralelos.
  • Desative seu software antivírus para arquivos .ilk, .pdb, .cc, .h e verifique apenas se há vírus em modificar.Desative a verificação do diretório onde residem suas fontes.Não faça nada estúpido.
  • Armazene e crie o código do Chromium em um segundo disco rígido.Isso realmente não acelerará a compilação, mas pelo menos seu computador permanecerá responsivo quando você fizer a sincronização do gclient ou uma compilação.
  • Desfragmente seu disco rígido regularmente.
  • Desative a memória virtual.

Outras dicas

Temos quase 100 projetos em uma solução e um tempo de desenvolvimento de apenas alguns segundos :)

Para compilações de desenvolvimento local, criamos um complemento do Visual Studio que altera Project references para DLL references e descarrega os projetos indesejados (e uma opção para desligá-los, é claro).

  • Construa toda a nossa solução uma vez
  • Descarregue os projetos nos quais não estamos trabalhando no momento e altere todas as referências do projeto para referências DLL.
  • Antes do check-in, altere todas as referências de DLL para referências de projeto.

Nossas compilações agora levam apenas alguns segundos quando trabalhamos em apenas alguns projetos por vez.Também podemos depurar os projetos adicionais, pois eles estão vinculados às DLLs de depuração.A ferramenta normalmente leva de 10 a 30 segundos para fazer um grande número de alterações, mas você não precisa fazer isso com tanta frequência.

Atualização de maio de 2015

O acordo que fiz (nos comentários abaixo) foi que eu lançaria o plugin para Open Source se atrai bastante interesse.4 anos depois, ele tem apenas 44 votos (e o Visual Studio agora tem duas versões subsequentes), portanto é atualmente um projeto de baixa prioridade.

Tive um problema semelhante em uma solução com 21 projetos e 1/2 milhão de LOC.A maior diferença foi obter discos rígidos mais rápidos.No monitor de desempenho, o 'Avg.Disk Queue 'aumentaria significativamente no laptop, indicando que o disco rígido era o gargalo.

Aqui estão alguns dados para o tempo total de reconstrução ...

1) Laptop, Core 2 Duo 2 GHz, unidade de 5400 RPM (não tenho certeza do cache.Era o Dell Inspiron padrão).

Tempo de reconstrução = 112 segundos.

2) Desktop (edição padrão), Core 2 Duo 2,3 GHz, unidade única de 7200 RPM e 8 MB de cache.

Tempo de reconstrução = 72 segundos.

3) Desktop Core 2 Duo 3Ghz, WD Raptor único de 10.000 RPM

Tempo de reconstrução = 39 segundos.

A unidade de 10.000 RPM não pode ser subestimada.As compilações foram significativamente mais rápidas e todo o resto, como a exibição de documentação, o uso do explorador de arquivos foi notado mais rápido.Foi um grande aumento de produtividade ao acelerar o ciclo de construção de código e execução.

Dado o que as empresas gastam em salários de desenvolvedores, é insano quanto eles podem desperdiçar comprando equipá-los com os mesmos PCs que a recepcionista usa.

Para compilações C# .NET, você pode usar Demônio .NET.É um produto que assume o processo de construção do Visual Studio para torná-lo mais rápido.

Ele faz isso analisando as alterações feitas e cria apenas o projeto que você realmente alterou, bem como outros projetos que realmente dependeram das alterações feitas.Isso significa que se você alterar apenas o código interno, apenas um projeto precisará ser compilado.

Desligue seu antivírus.Ele adiciona idades ao tempo de compilação.

Use compilação distribuída.Xorax IncrediBuild pode reduzir o tempo de compilação para alguns minutos.

Eu o usei em uma enorme solução C\C++ que geralmente leva de 5 a 6 horas para ser compilada.O IncrediBuild ajudou a reduzir esse tempo para 15 minutos.

Instruções para reduzir o tempo de compilação do Visual Studio para alguns segundos

Infelizmente, o Visual Studio não é inteligente o suficiente para distinguir as alterações na interface de um assembly das alterações inconsequentes no corpo do código.Esse fato, quando combinado com grandes soluções interligadas, pode às vezes criar uma tempestade perfeita de 'compilação completa' indesejadas quase toda vez que você altera uma única linha de código.

Uma estratégia para superar isso é desabilitar a construção automática da árvore de referência.Para fazer isso, use o 'Gerenciador de configuração' (Build / Configuration Manager... então, no menu suspenso Configuração da solução ativa, escolha 'Novo') para criar uma nova configuração de compilação chamada 'ManualCompile' que copia da configuração de depuração, mas faça não marque a caixa de seleção 'Criar novas configurações de projeto'.Nesta nova configuração de compilação, desmarque todos os projetos para que nenhum deles seja compilado automaticamente.Salve esta configuração clicando em 'Fechar'.Essa nova configuração de build é adicionada ao seu arquivo de solução.

Você pode alternar de uma configuração de compilação para outra por meio do menu suspenso de configuração de compilação na parte superior da tela do IDE (aquele que geralmente mostra 'Depurar' ou 'Liberar').Efetivamente, esta nova configuração de compilação do ManualCompile tornará inúteis as opções do menu Build para:'Construir Solução' ou 'Reconstruir Solução'.Assim, quando você estiver no modo ManualCompile, deverá construir manualmente cada projeto que estiver modificando, o que pode ser feito clicando com o botão direito em cada projeto afetado no Solution Explorer e selecionando 'Build' ou 'Rebuild'.Você verá que seus tempos gerais de compilação serão agora de meros segundos.

Para que esta estratégia funcione, é necessário que o VersionNumber encontrado nos arquivos AssemblyInfo e GlobalAssemblyInfo permaneça estático na máquina do desenvolvedor (não durante as compilações de lançamento, é claro) e que você não assine suas DLLs.

Um risco potencial de usar esta estratégia ManualCompile é que o desenvolvedor pode esquecer de compilar os projetos necessários e, ao iniciar o depurador, obter resultados inesperados (incapaz de anexar o depurador, arquivos não encontrados, etc.).Para evitar isso, provavelmente é melhor usar a configuração de compilação 'Debug' para compilar um esforço de codificação maior e usar apenas a configuração de compilação ManualCompile durante testes de unidade ou para fazer alterações rápidas de escopo limitado.

Se for C ou C++ e você não estiver usando cabeçalhos pré-compilados, deveria estar.

Tínhamos mais de 80 projetos em nossa solução principal que demoravam cerca de 4 a 6 minutos para serem construídos, dependendo do tipo de máquina que o desenvolvedor estava trabalhando.Consideramos isso muito longo:para cada teste, isso realmente consome seus FTEs.

Então, como obter tempos de construção mais rápidos?Como você já sabe, é o número de projetos que realmente prejudica o tempo de construção.É claro que não queríamos nos livrar de todos os nossos projetos e simplesmente juntar todos os arquivos de origem em um só.Mas tínhamos alguns projetos que poderíamos combinar mesmo assim:Como cada "projeto de repositório" na solução tinha seu próprio projeto unittest, simplesmente combinamos todos os projetos unittest em um projeto global unittest.Isso reduziu o número de projetos com cerca de 12 projetos e de alguma forma economizou 40% do tempo para construir a solução inteira.

Estamos pensando em outra solução.

Você também tentou configurar uma nova (segunda) solução com um novo projeto?Esta segunda solução deve simplesmente incorporar todos os arquivos usando pastas de solução.Porque você pode se surpreender ao ver o tempo de construção dessa nova solução com apenas um projeto.

No entanto, trabalhar com duas soluções diferentes exigirá uma consideração cuidadosa.Os desenvolvedores podem estar inclinados a realmente trabalhar na segunda solução e negligenciar completamente a primeira.Como a primeira solução com mais de 70 projetos será a solução que cuidará da sua hierarquia de objetos, esta deve ser a solução onde o seu buildserver deverá executar todos os seus unittests.Portanto o servidor para Integração Contínua deve ser o primeiro projeto/solução.Você tem que manter sua hierarquia de objetos, certo.

A segunda solução com apenas um projeto (que construirá muito mais rápido) será o projeto onde o teste e a depuração serão feitos por todos os desenvolvedores.Você tem que cuidar deles olhando para o buildserver!Se alguma coisa quebrar, DEVE ser consertada.

Certifique-se de que suas referências sejam referências do projeto e não diretamente às DLLs nos diretórios de saída da biblioteca.

Além disso, configure-os para não copiar localmente, exceto quando for absolutamente necessário (o projeto EXE mestre).

Postei esta resposta originalmente aqui:https://stackoverflow.com/questions/8440/visual-studio-optimizations#8473Você pode encontrar muitas outras dicas úteis nessa página.

Se você estiver usando o Visual Studio 2008, poderá compilar usando o sinalizador /MP para criar um único projeto em paralelo.Eu li que esse também é um recurso não documentado no Visual Studio 2005, mas nunca tentei.

Você pode construir vários projetos em paralelo usando o sinalizador /M, mas isso geralmente já está definido para o número de núcleos disponíveis na máquina, embora isso se aplique apenas ao VC++, acredito.

Percebo que essa pergunta é antiga, mas o assunto ainda é de interesse hoje.O mesmo problema me afetou ultimamente, e as duas coisas que mais melhoraram o desempenho da construção foram (1) usar um disco dedicado (e rápido) para compilar e (2) usar a mesma pasta de saída para todos os projetos e definir CopyLocal como False no projeto referências.

Alguns recursos adicionais:

Algumas ferramentas de análise:

Ferramentas-> Opções-> Configurações do projeto VC ++-> Timing de construção = Yes dirá que você cria tempo para cada VCProj.

Adicionar /Bt mude para a linha de comando do compilador para ver quanto cada arquivo CPP demorou

Usar /showIncludes para capturar inclusões aninhadas (arquivos de cabeçalho que incluem outros arquivos de cabeçalho) e ver quais arquivos podem economizar muito IO usando declarações futuras.

Isso o ajudará a otimizar o desempenho do compilador, eliminando dependências e consumo de desempenho.

Antes de gastar dinheiro para investir em discos rígidos mais rápidos, tente construir seu projeto inteiramente em um disco RAM (supondo que você tenha RAM sobrando).Você pode encontrar vários drivers de disco RAM gratuitos na rede.Você não encontrará nenhuma unidade física, incluindo SSDs, que seja mais rápida que um disco RAM.

No meu caso, um projeto que levou 5 minutos para ser construído em um i7 de 6 núcleos em uma unidade SATA de 7200 RPM com Incredibuild foi reduzido em apenas cerca de 15 segundos usando um disco RAM.Considerando a necessidade de copiar novamente para armazenamento permanente e o potencial de perda de trabalho, 15 segundos não são incentivo suficiente para usar um disco RAM e provavelmente não são muito incentivos para gastar várias centenas de dólares em uma unidade SSD ou de alta RPM.

O pequeno ganho pode indicar que a compilação estava vinculada à CPU ou que o cache de arquivos do Windows foi bastante eficaz, mas como ambos os testes foram feitos em um estado em que os arquivos não estavam armazenados em cache, inclino-me fortemente para as compilações vinculadas à CPU.

Dependendo do código real que você está compilando, sua milhagem pode variar – então não hesite em testar.

Qual é o tamanho do seu diretório de construção depois de fazer uma construção completa?Se você seguir a configuração padrão, cada assembly que você construir copiará todas as DLLs de suas dependências e as dependências de suas dependências, etc.para seu diretório bin.Em meu trabalho anterior, ao trabalhar com uma solução de cerca de 40 projetos, meus colegas descobriram que, de longe, a parte mais cara do processo de construção era copiar esses assemblies repetidas vezes, e que uma construção poderia gerar gigabytes de cópias das mesmas DLLs repetidas vezes. mais uma vez.

Aqui estão alguns conselhos úteis de Patrick Smacchia, autor de NDepend, sobre o que ele acredita que deveria ou não ser montagens separadas:

http://codebetter.com/patricksmacchia/2008/12/08/advices-on-partitioning-code-through-net-assemblies/

Existem basicamente duas maneiras de contornar isso, e ambas têm desvantagens.Uma delas é reduzir o número de montagens, o que obviamente dá muito trabalho.Outra é reestruturar seus diretórios de construção para que todas as suas pastas bin sejam consolidadas e os projetos não copiem as DLLs de suas dependências - eles não precisam fazer isso porque já estão todos no mesmo diretório.Isso reduz drasticamente o número de arquivos criados e copiados durante uma compilação, mas pode ser difícil de configurar e pode deixar você com alguma dificuldade em extrair apenas as DLLs exigidas por um executável específico para empacotamento.

Talvez pegue algumas funções comuns e crie algumas bibliotecas, para que as mesmas fontes não sejam compiladas repetidamente para vários projetos.

Se você está preocupado com a confusão de diferentes versões de DLLs, use bibliotecas estáticas.

Desative a integração VSS.Você pode não ter opção de usá-lo, mas as DLLs são renomeadas "acidentalmente" o tempo todo...

E definitivamente verifique as configurações do cabeçalho pré-compilado.O guia de Bruce Dawson é um pouco antigo, mas ainda assim muito bom - confira: http://www.cygnus-software.com/papers/precompiledheaders.html

Eu tenho um projeto que tem 120 ou mais exes, libs e dlls e leva um tempo considerável para ser construído.Eu uso uma árvore de arquivos em lote que chama arquivos make de um arquivo em lote mestre.Já tive problemas com coisas estranhas de cabeçalhos incrementais (ou temperamentais) no passado, então os evito agora.Raramente faço uma construção completa e geralmente deixo para o final do dia enquanto caminho por uma hora (então só posso adivinhar que leva cerca de meia hora).Então eu entendo por que isso é impraticável para trabalhar e testar.

Para trabalhar e testar, tenho outro conjunto de arquivos em lote para cada aplicativo (ou módulo ou biblioteca), que também possui todas as configurações de depuração em vigor - mas ainda chamam os mesmos arquivos make.Posso ativar ou desativar o DEBUG de tempos em tempos e também decidir sobre compilações ou marcas ou se desejo também criar bibliotecas das quais o módulo possa depender, e assim por diante.

O arquivo em lote também copia o resultado concluído nas (ou várias) pastas de teste.Dependendo das configurações, isso é concluído em alguns segundos a um minuto (em vez de meia hora).

Usei um IDE diferente (Zeus), pois gosto de ter controle sobre coisas como arquivos .rc e, na verdade, prefiro compilar a partir da linha de comando, embora esteja usando compiladores MS.

Fico feliz em postar um exemplo deste arquivo em lote se alguém estiver interessado.

Desative a indexação do sistema de arquivos em seus diretórios de origem (especificamente os diretórios obj se você quiser que sua fonte seja pesquisável)

Se este for um aplicativo da web, definir a construção em lote como true pode ajudar dependendo do cenário.

<compilation defaultLanguage="c#" debug="true" batch="true" >  

Você pode encontrar uma visão geral aqui: http://weblogs.asp.net/bradleyb/archive/2005/12/06/432441.aspx

Você também pode querer verificar referências de projetos circulares.Foi um problema para mim uma vez.

Aquilo é:

Projeto A faz referência ao Projeto B

Projeto B faz referência ao Projeto C

Projeto C faz referência ao Projeto A

Uma alternativa mais barata ao Xoreax IB é o uso do que chamo de compilações de uber-file.É basicamente um arquivo .cpp que possui

#include "file1.cpp"
#include "file2.cpp"
....
#include "fileN.cpp"

Então você compila as unidades uber em vez dos módulos individuais.Vimos tempos de compilação de 10 a 15 minutos a 1 a 2 minutos.Talvez você precise experimentar quantos #includes por arquivo uber fazem sentido.Depende dos projetos.etc.Talvez você inclua 10 arquivos, talvez 20.

Você paga um custo, então tome cuidado:

  1. Você não pode clicar com o botão direito em um arquivo e dizer "compilar...", pois precisa excluir os arquivos cpp individuais da compilação e incluir apenas os arquivos uber cpp
  2. Você deve ter cuidado com conflitos de variáveis ​​​​globais estáticas.
  3. Ao adicionar novos módulos, você deve manter os arquivos uber atualizados

É meio chato, mas para um projeto que é amplamente estático em termos de novos módulos, o esforço inicial pode valer a pena.Já vi esse método vencer o IB em alguns casos.

Se for um projeto C++, você deverá usar cabeçalhos pré-compilados.Isso faz uma enorme diferença nos tempos de compilação.Não tenho certeza do que cl.exe está realmente fazendo (sem usar cabeçalhos pré-compilados), parece estar procurando grande quantidade de cabeçalhos STL em todos os lugares errados antes de finalmente ir para o local correto.Isso adiciona segundos inteiros a cada arquivo .cpp que está sendo compilado.Não tenho certeza se este é um bug cl.exe ou algum tipo de problema STL no VS2008.

Olhando para a máquina na qual você está construindo, ela está configurada de maneira ideal?

Acabamos de completar o tempo de construção do nosso maior produto C++ em escala empresarial, de 19 horas para 16 minutos garantindo que o driver de filtro SATA correto foi instalado.

Sutil.

Há uma opção /MP não documentada no Visual Studio 2005, consulte http://lahsiv.net/blog/?p=40, o que permitiria a compilação paralela com base em arquivo, e não com base em projeto.Isso pode acelerar a compilação do último projeto ou, se você compilar um projeto.

Ao escolher uma CPU:O tamanho do cache L1 parece ter um grande impacto no tempo de compilação.Além disso, geralmente é melhor ter 2 núcleos rápidos do que 4 lentos.O Visual Studio não usa os núcleos extras de maneira muito eficaz.(Baseio isso na minha experiência com o compilador C++, mas provavelmente também é verdade para o compilador C#.)

Também estou convencido de que há um problema com o VS2008.Estou executando-o em um laptop Intel dual core com 3G Ram, com antivírus desligado.Compilar a solução geralmente é bastante inteligente, mas se eu estiver depurando, uma recompilação subsequente geralmente ficará lenta.Fica claro pela luz contínua do disco principal que há um gargalo de E/S do disco (você também pode ouvi-lo).Se eu cancelar a construção e desligar o VS, a atividade do disco será interrompida.Reinicie o VS, recarregue a solução e depois reconstrua, e é muito mais rápido.Até a próxima vez

Minha opinião é que este é um problema de paginação de memória - o VS simplesmente fica sem memória e o sistema operacional inicia a troca de páginas para tentar liberar espaço, mas o VS está exigindo mais do que a troca de páginas pode oferecer, então ele fica lento.Não consigo pensar em nenhuma outra explicação.

VS definitivamente não é uma ferramenta RAD, não é?

Por acaso sua empresa usa a Entrust para sua solução de PKI/criptografia?Acontece que estávamos tendo um desempenho de construção péssimo para um site bastante grande construído em C#, demorando mais de 7 minutos em um Rebuild-All.

Minha máquina é um i7-3770 com 16 GB de RAM e um SSD de 512 GB, então o desempenho não deveria ter sido tão ruim.Percebi que meus tempos de construção eram incrivelmente mais rápidos em uma máquina secundária mais antiga que construía a mesma base de código.Então, iniciei o ProcMon em ambas as máquinas, criei o perfil das compilações e comparei os resultados.

Veja só, a máquina de desempenho lento tinha uma diferença - uma referência ao Entrust.dll no stacktrace.Usando essas informações recém-adquiridas, continuei pesquisando no StackOverflow e encontrei isto: MSBUILD (VS2010) muito lento em algumas máquinas.De acordo com a resposta aceita, o problema reside no fato de o manipulador Entrust estar processando as verificações de certificado .NET em vez do manipulador nativo da Microsoft.Também é sugerido que o Entrust v10 resolva esse problema que prevalece no Entrust 9.

Atualmente, ele foi desinstalado e meu tempo de construção caiu para 24 segundos.YYMV com o número de projetos que você está construindo atualmente e pode não resolver diretamente o problema de dimensionamento que você estava perguntando.Publicarei uma edição nesta resposta se puder fornecer uma correção sem recorrendo à desinstalação do software.

É certo que há um problema com o VS2008.Porque a única coisa que fiz foi instalar o VS2008 para atualizar meu projeto que foi criado com o VS2005.Eu só tenho 2 projetos em minha solução.Não é grande.Compilação com VS2005:30 Secondes Compilation com VS2008:5 minutos

Boas sugestões que ajudaram até agora (sem dizer que não há outras boas sugestões abaixo, se você estiver com problemas, recomendo a leitura então, apenas o que nos ajudou)

  • Novo laptop de 3 GHz – o poder da utilização perdida faz maravilhas quando se trata de gerenciamento
  • Desative o antivírus durante a compilação
  • 'Desconectando' do VSS (na verdade, a rede) durante a compilação - posso fazer com que removamos completamente a integração VS-VSS e continuemos usando a UI do VSS

Ainda não estou cheirando uma compilação, mas tudo ajuda.

Também estamos testando a prática de construir novas áreas do aplicativo em novas soluções, importando as DLLs mais recentes conforme necessário, integrando-as à solução maior quando estivermos satisfeitos com elas.

Também podemos fazer o mesmo com o código existente, criando soluções temporárias que apenas encapsulam as áreas nas quais precisamos trabalhar e descartando-as após reintegrar o código.Precisamos pesar o tempo que levará para reintegrar esse código em relação ao tempo que ganhamos por não ter experiências como Rip Van Winkle com recompilação rápida durante o desenvolvimento.

Orion mencionou em um comentário que os genéricos também podem ter uma função.Pelos meus testes, parece haver um impacto mínimo no desempenho, mas não alto o suficiente para garantir - os tempos de compilação podem ser inconsistentes devido à atividade do disco.Devido a limitações de tempo, meus testes não incluíram tantos genéricos, ou tanto código, como apareceria no sistema ativo, então isso pode se acumular.Eu não evitaria usar genéricos onde eles deveriam ser usados, apenas para desempenho em tempo de compilação

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top