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?
fonte
(declare (optimize ...))
,(declare (<type> <var))
e(the <type> <expr>)
em suas funções? Caso contrário, é quase uma comparação justa :)Respostas:
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:
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.
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.
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
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.
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:
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.
fonte