Compiladores LISP / Scheme de qualidade para competir com C / C ++

8

Teoricamente falando, é possível ter um compilador Lisp / Scheme que possa produzir código que possa competir com o C compilado, digamos com margem de 15 a 25%?

Nos meus testes, descobri que a atual safra de compiladores (Bigloo, SBCL, Gambit, Chicken etc.) é 20 a 50 vezes mais lenta que o código C equivalente .

O único discrepante é o compilador Stalin . Para programas simples, ele produz binários equivalentes a C. No entanto, o que acho suspeito é que nenhum dos outros projetos (Bigloo, Chicken, Clozure etc.) tentou implementar os truques que Stalin usa ("otimização completa do programa", etc).

Sou um grande fã do LISP desde meados da década de 90 e gostaria de trazê-lo para a equipe, para que minha equipe possa iniciar projetos na metade do tempo em normalmente leva usando C / C ++ /. NET / etc, mas ... o desempenho questões são um enorme obstáculo.

Gostaria de saber se a falta de compiladores LISP de qualidade se deve ao fato de que não foram investidos tempo e dinheiro sérios no assunto OU se essa simplesmente não é uma tarefa viável, dado o estado atual da tecnologia do compilador?

user6703
fonte
1
Eu acredito que você testou os compiladores Lisp comum com (declare (optimize ...)), (declare (<type> <var))e (the <type> <expr>)em suas funções? Caso contrário, é quase uma comparação justa :)
Jakub Ledl
1
Acho que cs.stackexchange.com/questions/842/… responde a esta pergunta.
Kyle Jones
@KyleJones Does it? Meu palpite é que, com otimizações máximas, o Common Lisp pode ficar dentro da margem especificada pelo OP, se não estiver mais perto.
Jakub Lédl 04/02
Apenas mudar a linguagem de programação nunca fará com que sua equipe produza quatro vezes mais código correto ao mesmo tempo. O que os estudos mostraram é que os programadores experientes no idioma produzem aproximadamente o mesmo número de linhas de código por unidade de tempo para a complexidade do problema fixo, independente do idioma. Portanto, você não ganhará nada a menos que na sua área problemática os programas LISP sejam muito mais curtos. Outra coisa a considerar é que você precisa ter pessoas com experiência no LISP para desenvolvimento e manutenção. E esses estão muito no meio.
21813 vonbrand
1
Parece-me que esta pergunta exige mais experiência do programador do que resposta científica. Portanto, este pode ser o site errado para a pergunta.
Raphael

Respostas:

7

Como observado nos comentários, não é muito exato afirmar "a atual safra de compiladores (Bigloo, SBCL, Gambit, Chicken, etc) é 20 a 50 vezes mais lenta que o código C equivalente", sem qualificar como você testou e o que você testou.

Para meu uso, acho que, para muitas coisas, o Gambit e o Chicken Scheme estão bastante próximos do código 'C' equivalente em velocidade, com a versão atual do Gambit (4.7.3) geralmente mais rápida que o Chicken (4.9.0.1), mas acima da pré -optimizingo código 'C' de saída (fazendo suposições sobre o número de registros disponíveis - assume x686 - e forçando o uso de memória da pilha para quaisquer requisitos de memória extra, que decisões devem ser deixadas para o compilador 'C' como o Chicken, que geralmente elimina o requisito para registros extras e combina etapas de processamento) para impedir que o compilador 'C' faça suas próprias otimizações, resultando em pequenos loops muito apertados sendo cerca de duas vezes mais lentos que os mesmos loops pequenos em 'C' (ou Chicken ); O Chicken apenas define quantas variáveis ​​locais para uma determinada função é adequada (principalmente usada imutável) e depende do compilador para otimizar a maioria delas. Frango não

EDIT_ADD: Eu fiz mais algumas pesquisas nas versões Chicken and Gambit-C Scheme e encontrei o seguinte:

  1. O Chicken é mais rápido que o Gambit para pequenos loops apertados pela razão acima (depende mais do compilador 'C' para otimizações sem fazer muito por si próprio e, portanto, tira melhor proveito dos registros extras x86-64), mas também porque não inclui uma verificação de manutenção da pilha "POLL" dentro dos loops, enquanto o Gambit inclui a verificação "POLL" dentro do loop. Mesmo quando isso não é acionado (o caso usual), são necessários vários ciclos de clock da CPU para determinar que nada é necessário (cerca de 6 ciclos). Um compilador futuro mais inteligente pode perceber que não há necessidade de fazer uma verificação de pilha quando estiver dentro de um loop apertado e não estiver executando operações de construção de pilha, fazendo-o imediatamente antes ou depois do loop e economizando esse tempo.

  2. As macro 'C' da Gambit fazem muito trabalho, como dito, na definição precisa de como as operações devem ser executadas, especialmente incluindo operações com tamanho de pilha fixo, e essas são provavelmente mais difíceis para o compilador 'C' otimizar; o uso mais eficiente de registros poderia reduzir o tempo de loop apertado em talvez 4 ciclos, o que, em combinação com o acima exposto, o colocaria no estádio Chicken.

  3. Não produza otimizações de "leitura / modificação / gravação" para as operações vetoriais que são modificadas no local e não produzem código para que o compilador faça isso. Um back-end mais inteligente, como o LLVM, quando usado com Haskell, faz esse tipo de coisa. Isso reduziria o uso do registro em um e o tempo de execução, usando apenas uma única instrução em vez de uma leitura separada, o cálculo da modificação e uma gravação no mesmo local; isso se tornaria apenas um cálculo da modificação (digamos um pouco ou) e, em seguida, uma modificação de leitura (| =) escreve uma única instrução. Isso pode acelerar a velocidade mais ou menos por um ciclo

  4. Ambos são digitados dinamicamente e processam bits de "tag" de dados como parte de seus dados. Nem são espertos o suficiente para loops apertados para reduzir as tags, executar o loop "sem tags" e adicionar as tags novamente para obter resultados do loop, nem produzem código em que o compilador 'C' possa fazer isso, embora combina essas operações para alguns casos. A otimização aqui pode reduzir os tempos de loop em alguns ciclos da CPU, dependendo do loop.

  5. Loops 'C' muito apertados podem levar cerca de 3,5 ciclos de clock da CPU por loop em uma CPU rápida que não tenha a velocidade de acesso ao cache de memória reduzida (como o AMD Bulldozer, que é duas vezes mais lento); o mesmo loop no Chicken atualmente leva cerca de 6 ciclos e o Gambit leva cerca de 16,9 ciclos. Com todas as otimizações descritas acima, não há razão para que essas implementações do Scheme não possam fazer isso, no entanto, é necessário algum trabalho:

  6. No caso do Gambit, o trabalho mais árduo pode ser melhorar a análise de fluxo para reconhecer quando nenhum teste "POLL" precisa ser inserido (ou seja, isso pode ser acionado por interrupção, embora o compilador permita que as interrupções sejam desativadas para alguns usos? ); a melhoria mais fácil seria apenas implementar melhor o uso de registros (por exemplo, reconhecer registros x86-64 melhor do que a arquitetura x686 padrão). Para ambos, melhor análise de fluxo para reconhecer que eles podem "desmarcar" os dados, especialmente "fixnum", "flonum" e vetoriais, para que essas operações não sejam necessárias dentro de ciclos estreitos e combinando instruções de leitura / modificação / gravação. Ambos os fins poderiam ser alcançados usando um back-end melhor, como o LLVM (não é uma quantidade trivial de trabalho, mas ambos já estão parcialmente lá).

Conclusão: o frango atualmente é cerca de 50% mais lento que o 'C' nos CPUs mais rápidos (não no meu Bulldozer, onde é mais ou menos a mesma velocidade devido à limitação do cache do código 'C) e o Gambit é cerca de 400% mais lento (apenas cerca de 125% mais lento no meu trator lento). No entanto, futuras melhorias nos compiladores podem reduzir isso, para que o código não seja mais lento que 'C' ou dentro da margem especificada pelo OP.

Uma linguagem mais complexa, como Haskell, ao usar o back-end LLVM, prestando muita atenção ao uso rigoroso (não é um problema com o esquema que sempre está ansioso por padrão) e usando estruturas de dados apropriadas (matrizes sem caixa ST, em vez de listas para loops apertados; um pouco aplicável ao esquema usando vetores), roda aproximadamente na mesma velocidade que 'C' se o back-end do LLVM for usado com otimizações completas. Se isso puder ser feito, o Scheme também poderá usar as melhorias do compilador acima.

NOVAMENTE, não há como 20 a 50 vezes mais lento quando usado com sinalizadores de otimização adequados. END_EDIT_ADD

Obviamente, todos os benchmarks serão invalidados se não se usar as configurações de otimização apropriadas para cada um, como faria em um ambiente de produção ...

Eu pensaria que o compilador comercial Chez Scheme estaria no campo da produção de alto desempenho, como Gambit e Chicken, por ser comercial, certamente tem algum "tempo e dinheiro investidos nele".

A única maneira de fazer com que o Gambit ou o Chicken funcionem tão devagar quanto "20 a 50 vezes mais devagar que 'C'" é não usar nenhuma configuração de otimização; nesse caso, eles geralmente não correm muito mais rápido do que o interpretado nos REPLs. - 10 vezes mais devagar do que usando corretamente essas configurações.

É possível que o OP não tenha testado usando essas configurações corretamente?

Se o OP quiser esclarecer seus procedimentos de teste, terei prazer em editar esta resposta para mostrar que pelo menos Gambit e Chicken não precisam ser tão lentos.

GordonBGood
fonte
Isso está com as verificações de tipo de tempo de execução desativadas? Eu não gostaria de fazer isso na produção, pois torna os bugs exploráveis ​​que antes não eram.
Demi
@ Demetri, com a maioria dos compiladores do Scheme, como Gambit-C, Chicken ou Bigloo, obtém uma velocidade três vezes maior para muitos benchmarks desativando todas as verificações em tempo de execução, mas o código ainda não é "20 a 50 vezes mais lento" como indicado pela pergunta do OP. De fato, muitas dessas verificações podem ser desativadas com segurança no código de produção após verificar problemas na depuração sem risco, escrevendo o código para que essas verificações sejam incorporadas ao código somente quando necessário.
GordonBGood
@ Demetri Eu posso atestar que o esquema de frango está no campo de 1,5-2,5x mais lento que C para código de referência, se compilado com otimizações. Mas sim, é terrivelmente lento se compilado sem otimizações. FWIW Eu obtenho melhores resultados usando fixnum-ops, unboxed-objects e permitindo a compilação de blocos, ou seja, melhor análise estática levando a melhores otimizações.
Morten Jensen
Estou mais preocupado com as verificações de segurança de tempo de execução - não gostaria que um bug que não aparecesse no teste fosse um estouro de buffer explorável. Obviamente, alguém ativaria otimizações.
Demi
@ Demetri compreensível. Minha experiência é que a sobrecarga das verificações em tempo de execução depende muito do tipo de código que você escreve. Às vezes, a sobrecarga é mais do que 10x que a execução sem verificações nos meus testes.
Morten Jensen