Aqui estão algumas descobertas minhas atualizadas, embora limitadas, com o GCC 4.7.2 e o Clang 3.2 para C ++.
ATUALIZAÇÃO: comparação do GCC 4.8.1 v clang 3.3 anexada abaixo.
ATUALIZAÇÃO: A comparação do GCC 4.8.2 v clang 3.4 é anexada a isso.
Eu mantenho uma ferramenta OSS criada para Linux com GCC e Clang e com o compilador da Microsoft para Windows. A ferramenta, coan, é um pré-processador e analisador de arquivos de origem C / C ++ e linhas de código de tais: seu perfil computacional é mais importante na análise e manipulação de arquivos de descida recursiva. O ramo de desenvolvimento (ao qual esses resultados se referem) compreende atualmente cerca de 11K LOC em cerca de 90 arquivos. Agora, ele é codificado em C ++, rico em polimorfismo e modelos, mas ainda está envolvido em muitos patches por seu passado não tão distante no C. hackeado em conjunto. A semântica de movimentos não é expressamente explorada. É de rosca única. Não dediquei nenhum esforço sério a otimizá-lo, enquanto a "arquitetura" permanece tão amplamente relacionada à tarefa.
Empreguei o Clang anterior ao 3.2 apenas como um compilador experimental porque, apesar de sua velocidade e diagnóstico de compilação superiores, seu suporte padrão C ++ 11 ficou aquém da versão contemporânea do GCC nos aspectos exercidos pelo coan. Com o 3.2, essa lacuna foi fechada.
O equipamento de teste do meu Linux para o desenvolvimento de coan atual processa aproximadamente 70K arquivos de origem em uma mistura de casos de teste do analisador de um arquivo, testes de estresse que consomem 1000s de arquivos e testes de cenário que consomem <1K arquivos. Além de relatar os resultados do teste, o chicote de fios acumula e exibe os totais de arquivos consumidos e o tempo de execução consumido em coan (apenas passa cada linha de comando coan para o time
comando Linux, captura e soma os números relatados). Os tempos são lisonjeados pelo fato de que qualquer número de testes que levam 0 tempo mensurável irá somar 0, mas a contribuição desses testes é insignificante. As estatísticas de tempo são exibidas no final make check
desta forma:
coan_test_timer: info: coan processed 70844 input_files.
coan_test_timer: info: run time in coan: 16.4 secs.
coan_test_timer: info: Average processing time per input file: 0.000231 secs.
Comparei o desempenho do equipamento de teste entre o GCC 4.7.2 e o Clang 3.2, todos iguais, exceto os compiladores. No Clang 3.2, não era mais necessária nenhuma diferenciação de pré-processador entre os trechos de código que o GCC compilaria e as alternativas do Clang. Criei na mesma biblioteca C ++ (GCC) em cada caso e executei todas as comparações consecutivamente na mesma sessão do terminal.
O nível de otimização padrão para minha versão é -O2. Também testei com êxito compilações em -O3. Testei cada configuração três vezes consecutivas e calculei a média dos três resultados, com os seguintes resultados. O número em uma célula de dados é o número médio de microssegundos consumidos pelo executável coan para processar cada um dos ~ 70K arquivos de entrada (leitura, análise e saída e diagnóstico de gravação).
| -O2 | -O3 |O2/O3|
----------|-----|-----|-----|
GCC-4.7.2 | 231 | 237 |0.97 |
----------|-----|-----|-----|
Clang-3.2 | 234 | 186 |1.25 |
----------|-----|-----|------
GCC/Clang |0.99 | 1.27|
É provável que qualquer aplicativo em particular tenha características que sejam injustas com os pontos fortes ou fracos de um compilador. O benchmarking rigoroso emprega diversas aplicações. Com isso em mente, as principais características desses dados são:
- A otimização de OO foi marginalmente prejudicial ao GCC
- Otimização de OO foi importante para Clang
- Na otimização de -O2, o GCC foi mais rápido que Clang com apenas um bigode
- Na otimização de -O3, o Clang era importante mais rápido que o GCC.
Uma comparação interessante adicional dos dois compiladores surgiu por acidente logo após essas descobertas. Coan liberalmente emprega ponteiros inteligentes e um deles é muito exercido no manuseio de arquivos. Esse tipo de ponteiro inteligente em particular havia sido digitado em versões anteriores para fins de diferenciação do compilador, para ser um std::unique_ptr<X>
se o compilador configurado tiver suporte suficientemente maduro para seu uso como esse e, caso contrário, um std::shared_ptr<X>
. O viés para std::unique_ptr
era tolo, já que esses ponteiros eram de fato transferidos, mas std::unique_ptr
parecia a opção mais adequada para substituir
std::auto_ptr
em um ponto em que as variantes do C ++ 11 eram novas para mim.
No decorrer de compilações experimentais para avaliar a necessidade contínua do Clang 3.2 por essa diferenciação e semelhante, construí inadvertidamente
std::shared_ptr<X>
quando pretendia criar std::unique_ptr<X>
e fiquei surpreso ao observar que o executável resultante, com otimização -O2 padrão, foi o mais rápido que eu tinha visto, às vezes atingindo 184 ms. por arquivo de entrada. Com essa alteração no código fonte, os resultados correspondentes foram estes;
| -O2 | -O3 |O2/O3|
----------|-----|-----|-----|
GCC-4.7.2 | 234 | 234 |1.00 |
----------|-----|-----|-----|
Clang-3.2 | 188 | 187 |1.00 |
----------|-----|-----|------
GCC/Clang |1.24 |1.25 |
Os pontos de observação aqui são:
- Agora nenhum compilador se beneficia da otimização -O3.
- O clang é melhor do que o GCC em cada nível de otimização.
- O desempenho do GCC é afetado apenas marginalmente pela alteração do tipo de ponteiro inteligente.
- O desempenho de -O2 de Clang é afetado de maneira importante pela alteração do tipo de ponteiro inteligente.
Antes e depois da alteração do tipo de ponteiro inteligente, o Clang pode criar um executável de bobina substancialmente mais rápido na otimização de -O3 e pode criar um executável igualmente mais rápido em -O2 e -O3 quando esse tipo de ponteiro é o melhor - std::shared_ptr<X>
- para o trabalho.
Uma pergunta óbvia que não sou competente para comentar é por que o
Clang deve conseguir uma velocidade de 25% -O2 no meu aplicativo quando um tipo de ponteiro inteligente muito usado é alterado de exclusivo para compartilhado, enquanto o GCC é indiferente para a mesma mudança. Tampouco sei se devo torcer ou vaiar a descoberta de que a otimização -O2 de Clang abriga uma sensibilidade tão grande à sabedoria de minhas escolhas de ponteiros inteligentes.
ATUALIZAÇÃO: GCC 4.8.1 v clang 3.3
Os resultados correspondentes agora são:
| -O2 | -O3 |O2/O3|
----------|-----|-----|-----|
GCC-4.8.1 | 442 | 443 |1.00 |
----------|-----|-----|-----|
Clang-3.3 | 374 | 370 |1.01 |
----------|-----|-----|------
GCC/Clang |1.18 |1.20 |
O fato de todos os quatro executáveis agora levarem um tempo médio muito maior do que o anteriormente para processar o arquivo 1 não reflete no desempenho dos compiladores mais recentes. Isso se deve ao fato de que o ramo de desenvolvimento posterior do aplicativo de teste adquiriu muita sofisticação de análise nesse meio tempo e paga por isso rapidamente. Somente as proporções são significativas.
Os pontos de observação agora não são surpreendentemente novos:
- O GCC é indiferente à otimização de -O3
- clang se beneficia muito marginalmente da otimização de -O3
- O clang supera o GCC por uma margem igualmente importante em cada nível de otimização.
Comparando esses resultados com os do GCC 4.7.2 e do clang 3.2, destaca-se que o GCC recuperou cerca de um quarto da liderança do clang em cada nível de otimização. Porém, como o aplicativo de teste foi bastante desenvolvido nesse meio tempo, não se pode atribuir isso com confiança a uma atualização na geração de código do GCC. (Dessa vez, observei o instantâneo do aplicativo a partir do qual os tempos foram obtidos e posso usá-lo novamente.)
ATUALIZAÇÃO: GCC 4.8.2 v 3.4
Concluí a atualização para o GCC 4.8.1 v Clang 3.3 dizendo que continuaria com o mesmo instantâneo coan para obter mais atualizações. Mas, em vez disso, decidi testar nesse instantâneo (rev. 301) e no último instantâneo de desenvolvimento que tenho que passa em seu conjunto de testes (rev. 619). Isso dá aos resultados um pouco de longitude, e eu tive outro motivo:
Minha postagem original observou que eu não havia me esforçado para otimizar a velocidade da bobina. Este ainda era o caso da rev. 301. No entanto, depois de montar o aparelho de temporização no equipamento de teste de coan, toda vez que eu executava o conjunto de testes, o impacto no desempenho das alterações mais recentes me encarava. Vi que muitas vezes era surpreendentemente grande e que a tendência era mais acentuadamente negativa do que me pareceu merecida por ganhos de funcionalidade.
Pela rev. 308, o tempo médio de processamento por arquivo de entrada no conjunto de testes quase dobrou desde a primeira postagem aqui. Naquele momento, revirei minha política de 10 anos de não me preocupar com o desempenho. Na série intensiva de revisões, o desempenho de 619 sempre foi considerado, e um grande número delas foi exclusivamente para reescrever os principais porta-cargas em linhas fundamentalmente mais rápidas (embora sem o uso de recursos não-padrão do compilador para fazê-lo). Seria interessante ver a reação de cada compilador a essa inversão de marcha,
Aqui está a matriz de tempos agora familiar para as versões mais recentes dos dois compiladores da rev.301:
coan - rev.301 resultados
| -O2 | -O3 |O2/O3|
----------|-----|-----|-----|
GCC-4.8.2 | 428 | 428 |1.00 |
----------|-----|-----|-----|
Clang-3.4 | 390 | 365 |1.07 |
----------|-----|-----|------
GCC/Clang | 1.1 | 1.17|
A história aqui é apenas marginalmente alterada de GCC-4.8.1 e Clang-3.3. A exibição do GCC é um pouco melhor. O de Clang é um pouco pior. O barulho poderia explicar isso. O Clang ainda se destaca -O2
e -O3
margens que não importariam na maioria das aplicações, mas importariam para algumas.
E aqui está a matriz para rev. 619
coan - rev.619 resultados
| -O2 | -O3 |O2/O3|
----------|-----|-----|-----|
GCC-4.8.2 | 210 | 208 |1.01 |
----------|-----|-----|-----|
Clang-3.4 | 252 | 250 |1.01 |
----------|-----|-----|------
GCC/Clang |0.83 | 0.83|
Tomando as figuras 301 e 619 lado a lado, vários pontos se manifestam.
Eu estava com o objetivo de escrever código mais rápido, e ambos os compiladores justificam enfaticamente meus esforços. Mas:
O GCC retribui esses esforços com muito mais generosidade do que Clang. Na -O2
otimização, a compilação 619 da Clang é 46% mais rápida que a compilação 301: na -O3
melhoria da Clang, 31%. Bom, mas em cada nível de otimização, a compilação 619 do GCC é duas vezes mais rápida que a 301.
O GCC mais do que inverte a anterior superioridade de Clang. E em cada nível de otimização, o GCC agora supera o Clang em 17%.
A capacidade de Clang na compilação 301 de obter mais alavancagem do que o GCC da -O3
otimização desapareceu na compilação 619. Nenhum compilador ganha significativamente de -O3
.
Fiquei suficientemente surpreso com essa inversão de fortunas que suspeitei ter feito acidentalmente uma compilação lenta do próprio clang 3.4 (desde que o construí da fonte). Então, refiz o teste 619 com o estoque da minha distribuição Clang 3.3. Os resultados foram praticamente os mesmos que para 3.4.
Assim, no que diz respeito à reação à inversão de marcha: nos números aqui, Clang se saiu muito melhor que o GCC na velocidade de arrancar meu código C ++ quando eu não estava ajudando. Quando decidi ajudar, o GCC fez um trabalho muito melhor do que Clang.
Não elevo essa observação a um princípio, mas tomo a lição de que "Qual compilador produz os melhores binários?" é uma pergunta que, mesmo que você especifique o conjunto de testes ao qual a resposta deve ser relativa, ainda não é uma questão clara de apenas cronometrar os binários.
O seu melhor binário é o binário mais rápido ou é o que melhor compensa o código mais barato? Ou compensa melhor o
código criado caro que prioriza a capacidade de manutenção e a reutilização em detrimento da velocidade? Depende da natureza e dos pesos relativos de seus motivos para produzir o binário e das restrições sob as quais você o faz.
De qualquer forma, se você se preocupa profundamente em criar os "melhores" binários, é melhor continuar verificando como as iterações sucessivas dos compiladores produzem sua idéia das "melhores" sobre as iterações sucessivas do seu código.