Eu concordo com Dietrich Epp: é uma combinação de várias coisas que tornam o GHC rápido.
Em primeiro lugar, Haskell é de alto nível. Isso permite que o compilador execute otimizações agressivas sem quebrar seu código.
Pense em SQL. Agora, quando escrevo uma SELECT
declaração, pode parecer um loop imperativo, mas não é . Pode parecer que ele faz loop em todas as linhas da tabela tentando encontrar a que corresponde às condições especificadas, mas, na verdade, o "compilador" (o mecanismo do DB) poderia estar fazendo uma pesquisa de índice - que possui características de desempenho completamente diferentes. Mas como o SQL é de alto nível, o "compilador" pode substituir algoritmos totalmente diferentes, aplicar múltiplos processadores ou canais de E / S ou servidores inteiros de forma transparente e muito mais.
Penso em Haskell como sendo o mesmo. Você pode pensar que acabou de pedir ao Haskell para mapear a lista de entrada para uma segunda lista, filtrar a segunda para uma terceira e depois contar quantos itens resultaram. Mas você não viu o GHC aplicar regras de reescrita de fusão por fluxo nos bastidores, transformando tudo em um único loop de código de máquina que faz todo o trabalho em uma única passagem pelos dados sem alocação - o tipo de coisa que seria ser entediante, propenso a erros e insustentável para escrever à mão. Isso só é realmente possível devido à falta de detalhes de baixo nível no código.
Outra maneira de ver isso pode ser ... por que Haskell não deveria ser rápido? O que faz isso deve torná-lo lento?
Não é uma linguagem interpretada como Perl ou JavaScript. Nem sequer é um sistema de máquina virtual como Java ou C #. Ele compila todo o caminho até o código de máquina nativo, portanto não há sobrecarga lá.
Diferente das linguagens OO [Java, C #, JavaScript…], o Haskell possui apagamento total do tipo [como C, C ++, Pascal…]. Toda a verificação de tipo ocorre apenas em tempo de compilação. Portanto, não há verificação de tipo em tempo de execução para atrasar você. (Nenhuma verificação de ponteiro nulo, nesse caso. Em Java, por exemplo, a JVM deve verificar ponteiros nulos e lançar uma exceção se você deferir um. Haskell não precisa se preocupar com essa verificação.)
Você diz que parece lento "criar funções dinamicamente em tempo de execução", mas se você observar com muito cuidado, na verdade não fará isso. Pode parecer que você faz, mas você não. Se você diz (+5)
, bem, isso está codificado no seu código-fonte. Não pode ser alterado no tempo de execução. Portanto, não é realmente uma função dinâmica. Até funções com curry estão realmente apenas salvando parâmetros em um bloco de dados. Todo o código executável realmente existe em tempo de compilação; não há interpretação em tempo de execução. (Diferente de outros idiomas que possuem uma "função eval").
Pense em Pascal. É velho e ninguém mais o usa, mas ninguém iria reclamar que Pascal é lento . Há muitas coisas para não gostar, mas a lentidão não é realmente uma delas. Haskell não está realmente fazendo tanto que é diferente de Pascal, além de ter coleta de lixo em vez de gerenciamento manual de memória. E dados imutáveis permitem várias otimizações para o mecanismo de GC [que avaliação preguiçosa complica um pouco].
Acho que o fato é que Haskell parece avançado, sofisticado e de alto nível, e todo mundo pensa: "uau, isso é realmente poderoso, deve ser incrivelmente lento! " Mas não é. Ou pelo menos, não é da maneira que você esperaria. Sim, ele tem um sistema de tipos incrível. Mas você sabe o que? Tudo isso acontece em tempo de compilação. No tempo de execução, ele se foi. Sim, permite que você construa ADTs complicados com uma linha de código. Mas você sabe o que? Um ADT é apenas um simples C comum union
de struct
s. Nada mais.
O verdadeiro assassino é uma avaliação preguiçosa. Quando você acertar a rigidez / preguiça do seu código, poderá escrever um código estupidamente rápido que ainda seja elegante e bonito. Mas se você errar, seu programa fica milhares de vezes mais lento , e não é realmente óbvio porque isso está acontecendo.
Por exemplo, escrevi um pequeno programa trivial para contar quantas vezes cada byte aparece em um arquivo. Para um arquivo de entrada de 25 KB, o programa levou 20 minutos para ser executado e engoliu 6 gigabytes de RAM! Isso é um absurdo !! Mas então percebi qual era o problema, adicionei um único padrão de batida e o tempo de execução caiu para 0,02 segundos .
Este é o lugar onde Haskell vai inesperadamente lentamente. E com certeza leva um tempo para se acostumar. Mas com o tempo, fica mais fácil escrever um código muito rápido.
O que torna o Haskell tão rápido? Pureza. Tipos estáticos. Preguiça. Mas, acima de tudo, sendo suficientemente alto para que o compilador possa alterar radicalmente a implementação sem quebrar as expectativas do seu código.
Mas acho que essa é apenas a minha opinião ...