O blog de Honza hubička ainda mais divertido com a construção e benchmarking do firefox com o gcc e o clang buy bitcoin com dinheiro

Treeherder Treeherder, o sistema de construção usado pelos desenvolvedores da Mozilla parece surpreendentemente flexível e divertido de usar. Tive a impressão de que é um grande problema alterar sua configuração e atualizar para o toolchain mais novo. Na verdade, é tão fácil quanto se pode esperar. Fiquei surpreso que com direitos mínimos eu posso fazer isso sozinho. Com um esboço de Nathan, decidi fazer o seguinte:

Eu tive que ver muitas animações de dança de gato, sacrificar algumas vacas para o Sr. Ignucius e substituir 5 arquivos de objetos pelo compilador Clang pré-compilado (estes contêm rotinas de renderização vetorizadas AVX otimizadas manualmente escritas nas extensões somente Clang de extensões de vetores GNU que explicarei mais tarde ). 4 dias depois resolvi a maioria dos problemas de desempenho (e aproveitei as festividades também).


Neste post discuto o que aprendi. Eu me concentro em builds com otimização de link-time (LTO) e otimização guiada por perfil (PGO) apenas porque é o que os builds oficiais usam agora e porque torna mais fácil obter comparações apple-to-apple. Eu também pretendo escrever sobre performance com -O2 e -O2 -flto. Aqui, o fato de ambos os compiladores diferirem na interpretação dos níveis de otimizações é mostrado.

A Treeherder tem até agora o melhor shopping de bitcoin de infraestrutura de benchmarking com o qual trabalhei. Ser capaz de executar benchmarks em um ambiente controlado e reproduzível é muito importante e eu realmente gosto de poder clicar em benchmarks individuais e ver o histórico e o ruído da linha principal do Firefox. Também é bonitinho que alguém possa marcar regressões individuais, associá-las a bugzilla e há serifas de desempenho fazendo isso.

Esta é uma captura de tela da página cat dançando comparando os tamanhos binários do binário GCC 8 LTO + PGO com o Clang 6 LTO + PGO e outras métricas de compilação. A versão real pode demorar um pouco para carregar e considerará todas as alterações como insignificantes, porque não possui dados de várias compilações. Na verdade, a captura de tela compara minha construção com o tronco de 28 de dezembro de 2018, que não está longe do meu ponto de ramificação.

Captura de tela do gato de dança comparsion do meu GCC 8 LTO + PGO com o Clang 6 LTO + PGO para a compilação oficial do ponto que eu criei. Dancing cat lhe dará muitas informações extras: você pode olhar para sub-testes e mostra testes onde as mudanças não são consideradas importantes ou dentro do ruído. Você também pode clicar para representar graficamente e ver o progresso ao longo do tempo.

• displaylist_mutate (6,5%) está medindo o tempo que leva para renderizar a página depois de alterar a lista de exibição. Parece um bom benchmark porque seu perfil é muito plano, o que também dificultou a identificação rápida do problema. Uma coisa que eu notei é que o binário de construção do GCC tem alguns processos aparecendo no perfil que não são mostrados no clang, então pode ser algum tipo de diferença de configuração.

No geral, não vejo nada fundamentalmente inferior nas capacidades de geração de código e de tempo de link do GCC em comparação com o Clang. Na verdade, a implementação do LTO escalável (originalmente chamado de investimento de bitcoin da OMS, veja também aqui) é mais agressiva sobre a análise de programas inteiros do que o ThinLTO da Clang (que por design faz tanto quanto possível no escopo da unidade de tradução, onde os trabalhadores da tradução podem escolher algum código de outras unidades de tradução conforme instruído por este vinculador). O design ThinLTO é inspirado no fato de que quase todos os benefícios de qualidade de código da LTO nos compiladores atuais se originam da remoção e inlining de código inacessível. Por outro lado, a otimização em todo o escopo do programa permite equilibrar melhor o tamanho e o desempenho do código e implementar mais transformações. Passei muito tempo otimizando o compilador para obter o escalonamento de WHOPR (o que, obviamente, ajudou a limpar o middle-end em geral). Estou feliz que até agora os tempos de construção com o GCC parecem muito competitivos e temos mais espaço para experimentar transformações avançadas de LTO.

Update: Eu dei segunda chance para displaylist_mutate e descobri que é realmente inline perdida. O inliner do GCC está um pouco afinado para o Firefox e pode trocar um pouco mais por velocidade. Usando -param inline-unit-growth = 40 – parameal-inlining-insns = 20 corrige a regressão e traz algumas melhorias realmente boas sobre os espectros. Enquanto o binário ainda é 22% menor que o Clang build. Se eu aumentar ainda mais os limites, obtenho ainda mais melhorias. Agora vou comemorar o final do ano e uma vez no próximo ano vou analisar isso e escrever mais.

Eu adicionei patch para desativar watchdog para obter dados de perfil coletados corretamente. Este é um problema que eu notei anteriormente e agora é bug 1516081 que é a minha primeira experiência com o procedimento de envio de patch do Firefox (Phabricator) que eu achei particularmente interessante, exigindo-me a sacrificar alguns jogos do meu telefone para instalar algum aplicativo de autenticação.

O código para adicionar necessário –param lto-partitions = 1 já existe, mas de alguma forma não está habilitado corretamente. Eu acho que não foi atualizado para new –enable-lto. O problema aqui é que o sandbox contém símbolos de definição de declaração de alto nível. Isto não é suportado para LTO (porque não há nenhuma maneira de dizer ao compilador que o símbolo existe) e é recomendado simplesmente desabilitar o LTO em tais casos. Este é agora o bug 1516803.

Resumindo, a Regra de Definição de C ++ (ODR) diz que você não deve ter mais de uma definição de mesmo nome. Isso é muito difícil de manter no programa do tamanho do Firefox, a menos que você seja muito consistente com namespaces. A violação de ODR pode levar a surpresas, onde, por exemplo, o método virtual acaba sendo despachado para um método virtual de classe completamente diferente, que por acaso se choca com o nome mangling. Isso é particularmente perigoso quando, como o Firefox faz, você vincula várias versões da mesma biblioteca em um único binário.

Esses avisos são detectados apenas com LTO. Comecei a olhar para as correções e descobri que o GCC 8 é pouco detalhado. Por exemplo, ele produz uma violação de ODR na própria classe e, em seguida, em cada método que a classe possui (porque seu parâmetro pointer é incompatível). Silenciei algumas das perdas para o GCC 9. O GCC 9 agora encontra 23 violações que são relatadas como bug 1516758. O GCC 8 reportou 97.

Primeiro bug sobre o qual eu escrevi anteriormente e está relacionado à maneira como o GCC se lembra das opções de otimização de gráficos de moedas de bitcoin para comandos de compilação individuais e os combina durante a otimização de link-time. Houve uma omissão na transformação que mescla construtores e destrutores estáticos juntos, o que fez com que ele escolhesse sinalizadores aleatórios. Por azar essas bandeiras passaram a incluir -mavx2 e, assim, os binários caíram na máquina Bulldozer que uso para construções remotas (eles provavelmente ainda funcionariam em Talos). É fácil trabalhar isso adicionando -fdisable-ipa-cdtor ao LDFLAGS.

O segundo bug é reproduzido apenas com o GCC 8 e o PGO. Aqui, o GCC decide entrar em linha com o que, por sua vez, desencadeia uma omissão no passo de análise da desvitualização. Normalmente, os métodos C ++ levam o ponteiro para os objetos correspondentes como esse ponteiro. Os Thunks são especiais porque eles usam o ponteiro ajustado por algum deslocamento. É preciso muito azar para acionar isso e obter um código errado e sou muito grato a Jakub Jelínek, que passou a tarde isolando um testcase.

Skia (melhorando rasterflood_gradient e tsvgx) O Skia é uma biblioteca de renderização gráfica que é compartilhada pelo Firefox e pelo Chrome. É responsável pelo desempenho em dois benchmarks: rasterflood_gradient e o subteste tsvgx com regressão massiva. Eu gosto de blobs rosa-orrange melhor e, portanto, olhou para rasterflood_gradient. O perfil oficial de construção era:

Eu olhei em fontes e notei dois hacks engraçados. Alguém ativou o atributo always_inline apenas para o Clang que eu fixei por esse patch. E aparentemente havia restos de hackers na cópia do Firefox do Skia (que nunca fazia parte do Skia oficial), desabilitando a otimização do AVX em todos os compiladores não-Clang. Corrigido por este patch. Esse patch também corrige o bitcoin de conversão, outro desativado always_inline com o comentário sobre a regressão do tempo de compilação com o GCC. Aqueles não se reproduziram para mim. Eu também experimentei configurar -mtune = haswell nesses arquivos, já que eu suspeitava que a vetorização AVX na sintonia genérica estivesse desativada – eu nunca tive uma idéia para testá-lo, pois esperava que as pessoas usassem -march = neste caso.

De perfis eu percebi que o resto da diferença é novamente causada por máquinas #ifdef. Este tempo não é tão fácil de desfazer. A versão para Firefox do Skia contém duas implementações dos loops internos. Um deles é o Clang usando apenas extensões vetoriais enquanto outro é usado para o GCC e o MSVC usando a API? Mmintrin.h oficial da Intel. A segunda versão nunca foi portada para o AVX e os loops avx / hsw ainda usavam o vetor SSE de 128 bits e a API compilada apenas com o novo ISA habilitado.

As extensões vetoriais do Clang são na verdade extensões do vetor GNU e, portanto, eu concluí que não seria tão difícil portar o Skia para o GCC novamente e experimentá-lo. São cerca de 3000 linhas de código. Eu consegui compilar com o GCC em uma ou duas horas, mas descobriu-se que mais trabalho seria necessário. Não faz sentido gastar muito tempo com isso, se não pode ser upstreamed, então eu pretendo discutir isso com os desenvolvedores do Skia.

Os elencos explícitos são requeridos intencionalmente porque a semântica contradiz o significado usual de C (remover o atributo vector, integer para float conversion iria acontecer) e é por isso que os usuários são o que eu posso comprar com bitcoins necessários para escrevê-lo manualmente. É engraçado que Clang na verdade também requeira o elenco quando ambos os vetores são OpenCL ou ambos são GNU. Só aceita a = b se um vetor é GNU e outro OpenCL. O Skia usa esses elencos frequentemente em lugares que chama de funções mmintrin.h.

teste de __m128i (curto a, curto b, curto c, curto d, curto e, curto f, curto g) {retorno _mm_set_epi16 (a, b, c, d, e, f, g, 255); } GCC 8 com genérico compila isso em uma seqüência bastante longa de operações inteiras, armazenar e carregar vetores, enquanto o Clang usa inteiro para armazenamentos SSE. Isso porque o GCC 8 ainda otimiza para o Bulldozer em seu modelo de ajuste genérico e o número inteiro para lojas de vetores é caro lá. Eu fiz um ajuste bastante detalhado do ajuste genérico para o GCC 8 e lembro que decidi manter este sinalizador, já que ele não estava prejudicando muitos CPUs novos e era importante para o Bulldozer, mas eu perdi o efeito para entregar o código vetorizado. Eu mudarei o sinalizador de afinação do GCC 9.

Tweaking train run Enquanto observava os problemas de desempenho da renderização svg, notei que o código é otimizado para tamanho porque nunca foi executado durante a execução do trem. Como o Clang não otimiza para regiões frias de tamanho, esse problema não é muito visível para os benchmarks da Clang, mas eu tenho displaylist_mutate.html, rasterflood_svg.html e hixie-007.html na execução do trem. Estes são os mesmos usados ​​por Talos, apenas modificados para rodar mais e fora do talos. Eu, portanto, não preenchi o relatório de bug para isso ainda.

Essas duas flags colocam cada função e variável em uma seção de linkers separada. Isso permite que o vinculador manipule com eles independentemente, o que é usado para remover código inacessível e também para dobrar código idêntico no Gold.Eles também têm sobrecarga adicionando preenchimento de alinhamento extra e impedindo que o montador use instruções breves de ramificação.

Para o GCC sem LTO, a otimização do linker economiza cerca de 5 MB de tamanho binário para o GCC e 8 MB para o Clang. Com LTO, no entanto, não faz muito sentido porque o compilador pode fazer essas transformações por conta própria. Embora o GCC possa não mesclar todas as funções com um link de montagem idêntico para detectar e vice-versa, é uma vantagem desativar esses sinalizadores, em cerca de 1 MB e melhores tempos de link. Este é agora o bug 1516842.

Clang ignora o fato de que nas bibliotecas ELF os símbolos podem ser interpostos por diferentes implementações. Ao comparar o desempenho do código PIC entre o compilador, é sempre bom usar – fno-semântica-interposição no GCC para obter uma comparação de maçã para maçã. Efeito no Firefox não é grande (cerca de 100Kb de tamanho binarry differnece) porque declara muitos símbolos como oculto, mas como obter conta bitcoin impede mais surpresas de performance.

Isso possibilita a realização de testes que não podem ser feitos executando-se benchmarks SPEC comuns, que são de ordem de magnitude menor, compatível com o padrão e escritos há muitos anos. O teste do mundo real é essencial para tornar a produção de recursos individuais (como LTO ou PGO) pronta e para ajustar a configuração do conjunto de ferramentas para uso prático.

Por essa razão, estou um pouco triste com a mudança para o compilador único. Não está claro para mim se o Firefox permanecerá portátil no ano que vem. Nathan escreveu um blog interessante " quando uma monocultura de implementação pode bitcoin 2015 ser a coisa certa". Eu entendo seus pontos, mas, por outro lado, parece que nenhum esforço super-humano foi necessário para obter um desempenho decente dos builds do GCC. Da minha experiência pessoal, a manutenção de uma base de código portátil pode não ser sempre divertida, mas paga a longo prazo.

Parece-me que seria bom comunicar melhores configurações de desempenho com autores de pacotes usados ​​por distros individuais. Acho que a maioria de nós não baixa o Firefox manualmente e simplesmente usa um fornecido pela distribuição Linux em particular. Vendo a comunicação que tive com Martins sobre os pacotes do SUSE e do RedHat, parece muito difícil para os empacotadores reproduzirem o build do Firefox PGO, que é crítico para obter um bom binário. Uma das coisas que melhorariam a situação é fazer com que o sistema de compilação falhe se a execução do trem falhar, o que preenchi como bug 1516872.

Então, para melhor ou para pior, o hack que eu criei foi para apropriar-se indevidamente da abstração de vetor SkNx existente (para me poupar o trabalho de ter que reimplementar um) que teria uma sintaxe mais compatível com a extensão vetorial de Clang e também trabalhar com ambos GCC e MSVC como um bônus. A desvantagem é que o SkNx não é otimizado para o AVX2, e eu não queria passar pelo problema de implementar suporte ao AVX2 para o SkNx. Assim, o nível de otimização só tira proveito do SSE2 e é por isso que o caminho AVX está desabilitado no GCC, pois há poucos casos em que ele ajudaria muito sem o AVX2.

No entanto, parece-me que a essência desse hack, simplesmente afastar a diferença entre as extensões de vetor GCC e Clang, empurrando ambas em classes wrapper, seria um meio mais feliz, se esses fossem os únicos compiladores que precisamos suportar mais , talvez com algum suporte a token para MSVC através dos intrínsecos nativos (SkNx ou outro). Isso parece mais razoável para mim do que tentar resolver isso no próprio nível do compilador. Responder Excluir