Peguei o Problema 12 do Project Euler como um exercício de programação e comparei minhas (certamente não ótimas) implementações em C, Python, Erlang e Haskell. Para obter tempos de execução mais altos, procuro o primeiro número do triângulo com mais de 1000 divisores em vez de 500, conforme indicado no problema original.
O resultado é o seguinte:
C:
lorenzo@enzo:~/erlang$ gcc -lm -o euler12.bin euler12.c
lorenzo@enzo:~/erlang$ time ./euler12.bin
842161320
real 0m11.074s
user 0m11.070s
sys 0m0.000s
Pitão:
lorenzo@enzo:~/erlang$ time ./euler12.py
842161320
real 1m16.632s
user 1m16.370s
sys 0m0.250s
Python com PyPy:
lorenzo@enzo:~/Downloads/pypy-c-jit-43780-b590cf6de419-linux64/bin$ time ./pypy /home/lorenzo/erlang/euler12.py
842161320
real 0m13.082s
user 0m13.050s
sys 0m0.020s
Erlang:
lorenzo@enzo:~/erlang$ erlc euler12.erl
lorenzo@enzo:~/erlang$ time erl -s euler12 solve
Erlang R13B03 (erts-5.7.4) [source] [64-bit] [smp:4:4] [rq:4] [async-threads:0] [hipe] [kernel-poll:false]
Eshell V5.7.4 (abort with ^G)
1> 842161320
real 0m48.259s
user 0m48.070s
sys 0m0.020s
Haskell:
lorenzo@enzo:~/erlang$ ghc euler12.hs -o euler12.hsx
[1 of 1] Compiling Main ( euler12.hs, euler12.o )
Linking euler12.hsx ...
lorenzo@enzo:~/erlang$ time ./euler12.hsx
842161320
real 2m37.326s
user 2m37.240s
sys 0m0.080s
Resumo:
- C: 100%
- Python: 692% (118% com PyPy)
- Erlang: 436% (135% graças a RichardC)
- Haskell: 1421%
Suponho que C tenha uma grande vantagem, pois usa muito para os cálculos e não inteiros de comprimento arbitrário como os outros três. Além disso, ele não precisa carregar um tempo de execução primeiro (os outros?).
Pergunta 1:
Erlang, Python e Haskell perdem velocidade devido ao uso de números inteiros de comprimento arbitrário ou não, desde que os valores sejam menores que MAXINT
?
Pergunta 2: Por que o Haskell é tão lento? Existe um sinalizador de compilador que desliga os freios ou é minha implementação? (O último é bastante provável, pois Haskell é um livro com sete selos para mim.)
Pergunta 3: Você pode me oferecer algumas dicas de como otimizar essas implementações sem alterar a maneira como determino os fatores? Otimização de qualquer forma: mais agradável, mais rápido, mais "nativo" para o idioma.
EDITAR:
Pergunta 4: Minhas implementações funcionais permitem o LCO (otimização da última chamada, também conhecida como eliminação da recursão da cauda) e, portanto, evitam adicionar quadros desnecessários à pilha de chamadas?
Eu realmente tentei implementar o mesmo algoritmo o mais semelhante possível nas quatro línguas, embora eu tenha que admitir que meu conhecimento de Haskell e Erlang é muito limitado.
Códigos-fonte usados:
#include <stdio.h>
#include <math.h>
int factorCount (long n)
{
double square = sqrt (n);
int isquare = (int) square;
int count = isquare == square ? -1 : 0;
long candidate;
for (candidate = 1; candidate <= isquare; candidate ++)
if (0 == n % candidate) count += 2;
return count;
}
int main ()
{
long triangle = 1;
int index = 1;
while (factorCount (triangle) < 1001)
{
index ++;
triangle += index;
}
printf ("%ld\n", triangle);
}
#! /usr/bin/env python3.2
import math
def factorCount (n):
square = math.sqrt (n)
isquare = int (square)
count = -1 if isquare == square else 0
for candidate in range (1, isquare + 1):
if not n % candidate: count += 2
return count
triangle = 1
index = 1
while factorCount (triangle) < 1001:
index += 1
triangle += index
print (triangle)
-module (euler12).
-compile (export_all).
factorCount (Number) -> factorCount (Number, math:sqrt (Number), 1, 0).
factorCount (_, Sqrt, Candidate, Count) when Candidate > Sqrt -> Count;
factorCount (_, Sqrt, Candidate, Count) when Candidate == Sqrt -> Count + 1;
factorCount (Number, Sqrt, Candidate, Count) ->
case Number rem Candidate of
0 -> factorCount (Number, Sqrt, Candidate + 1, Count + 2);
_ -> factorCount (Number, Sqrt, Candidate + 1, Count)
end.
nextTriangle (Index, Triangle) ->
Count = factorCount (Triangle),
if
Count > 1000 -> Triangle;
true -> nextTriangle (Index + 1, Triangle + Index + 1)
end.
solve () ->
io:format ("~p~n", [nextTriangle (1, 1) ] ),
halt (0).
factorCount number = factorCount' number isquare 1 0 - (fromEnum $ square == fromIntegral isquare)
where square = sqrt $ fromIntegral number
isquare = floor square
factorCount' number sqrt candidate count
| fromIntegral candidate > sqrt = count
| number `mod` candidate == 0 = factorCount' number sqrt (candidate + 1) (count + 2)
| otherwise = factorCount' number sqrt (candidate + 1) count
nextTriangle index triangle
| factorCount triangle > 1000 = triangle
| otherwise = nextTriangle (index + 1) (triangle + index + 1)
main = print $ nextTriangle 1 1
Euler12[x_Integer] := Module[{s = 1}, For[i = 2, DivisorSigma[0, s] < x, i++, s += i]; s]
. viva!Respostas:
Usando
GHC 7.0.3
,gcc 4.4.6
,Linux 2.6.29
em um Core 2 Duo (2,5 GHz) máquina x86_64, compilar utilizandoghc -O2 -fllvm -fforce-recomp
para Haskell egcc -O3 -lm
para C.-O3
)-O2
sinalizador)factorCount'
código não está explicitamente digitado e padronizadoInteger
(obrigado Daniel por corrigir meu erro de diagnóstico aqui!). Fornecer uma assinatura de tipo explícita (que é uma prática padrão de qualquer maneira) usandoInt
e o tempo muda para 11,1 segundosfactorCount'
você chamou desnecessariamentefromIntegral
. Uma correção resulta em nenhuma alteração (o compilador é inteligente, tem sorte para você).mod
onderem
é mais rápido e suficiente. Isso altera o tempo para 8,5 segundos .factorCount'
está constantemente aplicando dois argumentos extras que nunca mudam (number
,sqrt
). Uma transformação de trabalhador / invólucro nos fornece:Isso mesmo, 7,95 segundos . Consistentemente meio segundo mais rápido do que a solução C . Sem a
-fllvm
bandeira que ainda estou recebendo8.182 seconds
, o backend do NCG também está indo bem neste caso.Conclusão: Haskell é incrível.
Código resultante
EDIT: Então, agora que exploramos isso, vamos abordar as perguntas
Em Haskell, o uso
Integer
é mais lento do que oInt
quanto mais lento depende dos cálculos realizados. Felizmente (para máquinas de 64 bits)Int
é suficiente. Por questões de portabilidade, você provavelmente deve reescrever meu código para usarInt64
ouWord64
(C não é o único idioma com along
).Foi isso que eu respondi acima. A resposta foi
-O2
rem
nãomod
(uma otimização frequentemente esquecida) eSim, esse não era o problema. Bom trabalho e feliz por você ter considerado isso.
fonte
rem
na verdade é um subcomponente damod
operação (eles não são os mesmos). Se você procurar na biblioteca do GHC Base, verámod
testes para várias condições e ajusta o sinal de acordo. (vermodInt#
emBase.lhs
)mod
porrem
depois de ler esta resposta (heh, oops). Veja o link para meus horários, mas a versão curta é "quase idêntica à C".factorCount'
era recursivo de cauda, eu pensaria que o compilador pudesse identificar os parâmetros extras que não estavam sendo alterados e otimizar a recursão de cauda apenas para os parâmetros alterados (Haskell sendo uma linguagem pura, afinal, isso deve ser fácil). Alguém acha que o compilador poderia fazer isso ou devo voltar a ler mais artigos teóricos?Existem alguns problemas com a implementação do Erlang. Como linha de base para o seguinte, meu tempo de execução medido para o seu programa Erlang não modificado foi de 47,6 segundos, comparado a 12,7 segundos para o código C.
A primeira coisa que você deve fazer se desejar executar o código Erlang intensivamente computacional é usar o código nativo. Compilar com
erlc +native euler12
reduziu o tempo para 41,3 segundos. No entanto, essa é uma aceleração muito menor (apenas 15%) do que o esperado na compilação nativa nesse tipo de código, e o problema é seu uso-compile(export_all)
. Isso é útil para experimentação, mas o fato de todas as funções serem potencialmente acessíveis externamente faz com que o compilador nativo seja muito conservador. (O emulador BEAM normal não é muito afetado.) Substituir esta declaração-export([solve/0]).
fornece uma aceleração muito melhor: 31,5 segundos (quase 35% da linha de base).Mas o código em si tem um problema: para cada iteração no loop factorCount, você executa este teste:
O código C não faz isso. Em geral, pode ser complicado fazer uma comparação justa entre diferentes implementações do mesmo código e, em particular, se o algoritmo for numérico, porque você precisa ter certeza de que eles estão realmente fazendo a mesma coisa. Um ligeiro erro de arredondamento em uma implementação devido a alguma conversão de texto em algum lugar pode fazer com que ele faça muito mais iterações do que a outra, mesmo que ambas cheguem ao mesmo resultado.
Para eliminar essa possível fonte de erro (e se livrar do teste extra em cada iteração), reescrevi a função factorCount da seguinte maneira, modelada de perto no código C:
Essa reescrita, não
export_all
e compilação nativa me deu o seguinte tempo de execução:o que não é tão ruim se comparado ao código C:
considerando que Erlang não é voltado para escrever código numérico, sendo apenas 50% mais lento que C em um programa como esse é muito bom.
Por fim, em relação às suas perguntas:
Pergunta 1: Erlang, python e haskell perdem velocidade devido ao uso de números inteiros de comprimento arbitrário ou não, desde que os valores sejam menores que MAXINT?
Sim, um pouco. Em Erlang, não há como dizer "use aritmética de 32/64 bits com wrap-around", portanto, a menos que o compilador possa provar alguns limites em seus números inteiros (e geralmente não), ele deve verificar todos os cálculos para ver se eles podem caber em uma única palavra marcada ou se precisar transformá-los em bignums alocados em heap. Mesmo que nenhum bignum seja usado na prática em tempo de execução, essas verificações precisarão ser executadas. Por outro lado, isso significa que você sabe que o algoritmo nunca falhará por causa de um número inteiro inesperado se você repentinamente fornecer entradas maiores do que antes.
Pergunta 4: Minhas implementações funcionais permitem LCO e, portanto, evitam adicionar quadros desnecessários à pilha de chamadas?
Sim, seu código Erlang está correto em relação à otimização da última chamada.
fonte
No que diz respeito à otimização do Python, além de usar o PyPy (para acelerações impressionantes e sem alterações no código), você pode usar a cadeia de ferramentas de tradução do PyPy para compilar uma versão compatível com RPython ou o Cython para criar um módulo de extensão, ambos que são mais rápidos que a versão C nos meus testes, com o módulo Cython quase o dobro da velocidade . Para referência, incluí também os resultados de benchmark C e PyPy:
C (compilado com
gcc -O3 -lm
)PyPy 1.5
RPython (usando a revisão mais recente do PyPy
c2f583445aee
)Cython 0.15
A versão do RPython possui algumas alterações importantes. Para traduzir para um programa independente, você precisa definir o seu
target
, que nesse caso é amain
função. Espera-se que aceitesys.argv
como único argumento e é necessário retornar um int. Você pode traduzi-lo usando translate.py,% translate.py euler12-rpython.py
que é traduzido para C e o compila para você.A versão do Cython foi reescrita como um módulo de extensão
_euler12.pyx
, que eu importo e chamo de um arquivo python normal. O_euler12.pyx
é essencialmente o mesmo que a sua versão, com algumas declarações adicionais tipo estático. O setup.py possui o padrão normal para criar a extensão, usandopython setup.py build_ext --inplace
.Sinceramente, tenho muito pouca experiência com RPython ou Cython, e fiquei agradavelmente surpreso com os resultados. Se você estiver usando o CPython, escrever os bits de código com muita CPU em um módulo de extensão Cython parece uma maneira muito fácil de otimizar seu programa.
fonte
A implementação C é abaixo do ideal (como sugerido por Thomas M. DuBuisson), a versão usa números inteiros de 64 bits (ou seja, tipo de dados longo ). Investigarei a listagem de montagem mais tarde, mas com um palpite, há alguns acessos à memória no código compilado, o que torna o uso de números inteiros de 64 bits significativamente mais lento. É esse ou o código gerado (o fato de que você pode ajustar menos ints de 64 bits em um registro SSE ou arredondar um dobro para um número inteiro de 64 bits é mais lento).
Aqui está o código modificado (basta substituir long por int e eu expliquei explicitamente factorCount, embora eu não ache que isso seja necessário com gcc -O3):
Correr + cronometrar:
Para referência, a implementação de haskell por Thomas na resposta anterior fornece:
Conclusão: Tirando nada do ghc, é um ótimo compilador, mas o gcc normalmente gera código mais rápido.
fonte
2.5 seconds
enquanto uma modificação semelhante ao código Haskell (movendo-se para o Word32, adicionando pragma INLINE) resulta em um tempo de execução de4.8 seconds
. Talvez algo possa ser feito (não trivialmente, ao que parece) - o resultado do gcc é certamente impressionante.Dê uma olhada neste blog . Nos últimos anos, ele resolveu alguns dos problemas do Project Euler em Haskell e Python e, em geral, descobriu que Haskell era muito mais rápido. Eu acho que entre essas linguagens isso tem mais a ver com sua fluência e estilo de codificação.
Quando se trata de velocidade do Python, você está usando a implementação errada! Experimente o PyPy e, para coisas como essa, você descobrirá que é muito, muito mais rápido.
fonte
Sua implementação do Haskell pode ser bastante acelerada usando algumas funções dos pacotes Haskell. Nesse caso, usei primes, que são instalados apenas com 'cabal install primes';)
Horários:
Seu programa original:
Implementação aprimorada
Como você pode ver, este é executado em 38 milissegundos na mesma máquina em que o seu foi executado em 16 segundos :)
Comandos de compilação:
fonte
Apenas por diversão. A seguir, uma implementação Haskell mais 'nativa':
Usando
ghc -O3
, isso é executado de forma consistente em 0,55-0,58 segundos na minha máquina (Core i7 de 1,73 GHz).Uma função factorCount mais eficiente para a versão C:
Alterar longs para ints em main, usando
gcc -O3 -lm
, isso é executado consistentemente em 0,31-0,35 segundos.Ambos podem ser executados ainda mais rapidamente se você tirar proveito do fato de que o n-ésimo número do triângulo = n * (n + 1) / 2 e n e (n + 1) possuem fatorações primárias completamente díspares, portanto, o número de fatores de cada metade pode ser multiplicado para encontrar o número de fatores do todo. Os seguintes:
reduz o tempo de execução do código c para 0,17-0,19 segundos e pode lidar com pesquisas muito maiores - mais de 10000 fatores levam cerca de 43 segundos na minha máquina. Deixo uma aceleração de haskell semelhante para o leitor interessado.
fonte
Isso é improvável. Não posso dizer muito sobre Erlang e Haskell (bem, talvez um pouco sobre Haskell abaixo), mas posso apontar muitos outros gargalos no Python. Toda vez que o programa tenta executar uma operação com alguns valores no Python, deve verificar se os valores são do tipo apropriado e custa um pouco de tempo. Sua
factorCount
função apenas aloca uma lista comrange (1, isquare + 1)
vários horários, e amalloc
alocação de memória com estilo de execução é muito mais lenta do que iterando em um intervalo com um contador, como você faz em C. Notavelmente, issofactorCount()
é chamado várias vezes e, portanto, aloca muitas listas. Além disso, não devemos esquecer que o Python é interpretado e o intérprete do CPython não tem grande foco em ser otimizado.EDIT : oh, bem, notei que você está usando Python 3, portanto
range()
, não retorna uma lista, mas um gerador. Nesse caso, meu argumento sobre alocar listas está meio errado: a função apenas alocarange
objetos, que são ineficientes, mas não tão ineficientes quanto alocar uma lista com muitos itens.Você está usando abraços ? Abraços é um intérprete consideravelmente lento. Se você o estiver usando, talvez você possa se divertir melhor com o GHC - mas eu só estou cogitando de hipoteses, o tipo de coisa que um bom compilador Haskell faz sob o capô é bastante fascinante e está além da minha compreensão :)
Eu diria que você está jogando um jogo sem graça. A melhor parte de conhecer vários idiomas é usá-los da maneira mais diferente possível :) Mas discordo, não tenho nenhuma recomendação para esse ponto. Desculpe, espero que alguém possa ajudá-lo neste caso :)
Tanto quanto me lembro, você só precisa ter certeza de que sua chamada recursiva é o último comando antes de retornar um valor. Em outras palavras, uma função como a abaixo poderia usar essa otimização:
No entanto, você não teria essa otimização se sua função fosse como a abaixo, porque existe uma operação (multiplicação) após a chamada recursiva:
Separei as operações em algumas variáveis locais para deixar claro quais operações são executadas. No entanto, o mais comum é ver essas funções como abaixo, mas elas são equivalentes para o argumento que estou enfatizando:
Observe que cabe ao compilador / intérprete decidir se fará recursão final. Por exemplo, o intérprete Python não o faz se me lembro bem (usei o Python no meu exemplo apenas por causa de sua sintaxe fluente). De qualquer forma, se você encontrar coisas estranhas, como funções fatoriais, com dois parâmetros (e um dos parâmetros tiver nomes como
acc
,accumulator
etc.), agora você sabe por que as pessoas fazem isso :)fonte
Com Haskell, você realmente não precisa pensar em recursões explicitamente.
No código acima, substituí recursões explícitas na resposta do @Thomas por operações de lista comuns. O código ainda faz exatamente a mesma coisa sem nos preocuparmos com a recursão da cauda. Ele roda (~ 7.49s ) cerca de 6% mais lento que a versão na resposta do @Thomas (~ 7.04s ) na minha máquina com o GHC 7.6.2, enquanto a versão C do @Raedwulf executa ~ 3.15s . Parece que o GHC melhorou ao longo do ano.
PS. Sei que é uma pergunta antiga e me deparo com ela nas pesquisas do google (esqueci o que estava pesquisando agora ...). Só queria comentar a pergunta sobre LCO e expressar meus sentimentos sobre Haskell em geral. Eu queria comentar a resposta principal, mas os comentários não permitem blocos de código.
fonte
Mais alguns números e explicações para a versão C. Aparentemente, ninguém fez isso durante todos esses anos. Lembre-se de votar acima desta resposta para que ela fique no topo para que todos vejam e aprendam.
Etapa 1: Referência dos programas do autor
Especificações do laptop:
Comandos:
.
Os nomes de arquivos são:
integertype_architecture_compiler.exe
Etapa 2: Investigar, melhorar e comparar novamente
O VS é 250% mais rápido que o gcc. Os dois compiladores devem fornecer uma velocidade semelhante. Obviamente, algo está errado com o código ou as opções do compilador. Vamos investigar!
O primeiro ponto de interesse são os tipos inteiros. As conversões podem ser caras e a consistência é importante para uma melhor geração e otimizações de código. Todos os números inteiros devem ser do mesmo tipo.
É uma bagunça mista
int
elong
agora. Nós vamos melhorar isso. Que tipo de usar? O mais rápido. Tenho que avaliar todos eles!Tipos inteiros são
int
long
int32_t
uint32_t
int64_t
euint64_t
de#include <stdint.h>
Existem muitos tipos de números inteiros em C, além de alguns assinados / não assinados para brincar, além da opção de compilar como x86 ou x64 (que não deve ser confundido com o tamanho inteiro real). São muitas versões para compilar e executar ^^
Etapa três: Compreendendo os números
Conclusões definitivas:
Truque de pergunta: "Quais são os tamanhos de int e long em C?"
A resposta certa é: O tamanho de int e long em C não está bem definido!
A partir da especificação C:
Na página do manual do gcc (sinalizadores -m32 e -m64):
Na documentação do MSDN (intervalos de tipos de dados) https://msdn.microsoft.com/en-us/library/s3f49ktz%28v=vs.110%29.aspx :
Para concluir: lições aprendidas
Inteiros de 32 bits são mais rápidos que números inteiros de 64 bits.
Os tipos inteiros padrão não são bem definidos em C nem C ++, eles variam dependendo dos compiladores e arquiteturas. Quando você precisar de consistência e previsibilidade, use a
uint32_t
família inteira de#include <stdint.h>
.Problemas de velocidade resolvidos. Todos os outros idiomas estão de volta centenas por cento atrás, C & C ++ vencem novamente! Eles sempre fazem. A próxima melhoria será multithreading usando o OpenMP: D
fonte
INT_MIN
eINT_MAX
(-32767 e 32767, que praticamente impõem um requisito deint
pelo menos 16 bits).long
deve ser pelo menos tão grande quanto umint
, e a média dos requisitos de intervalolong
é de pelo menos 32 bits.Olhando para sua implementação Erlang. O tempo incluiu a inicialização de toda a máquina virtual, executando seu programa e interrompendo a máquina virtual. Tenho certeza de que configurar e interromper o erlang vm leva algum tempo.
Se o tempo fosse feito dentro da própria máquina virtual erlang, os resultados seriam diferentes, pois nesse caso teríamos tempo real apenas para o programa em questão. Caso contrário, acredito que o tempo total gasto pelo processo de inicialização e carregamento do Erlang Vm mais o de interrompê-lo (como você o coloca no seu programa) estão incluídos no tempo total que o método que você está usando para cronometrar o programa está sendo emitido. Considere usar o próprio tempo erlang que usamos quando queremos cronometrar nossos programas na própria máquina virtual
timer:tc/1 or timer:tc/2 or timer:tc/3
. Dessa maneira, os resultados do erlang excluirão o tempo necessário para iniciar e parar / interromper / interromper a máquina virtual. Esse é o meu raciocínio, pense nisso e tente seu benchmark novamente.Na verdade, sugiro que tentemos cronometrar o programa (para idiomas com tempo de execução), dentro do tempo de execução desses idiomas, a fim de obter um valor preciso. C, por exemplo, não tem sobrecarga de iniciar e desligar um sistema de tempo de execução, assim como Erlang, Python e Haskell (98% de certeza disso - eu estou corrigindo). Então (com base nesse raciocínio) concluo dizendo que esse benchmark não era preciso / justo o suficiente para idiomas rodando em cima de um sistema de tempo de execução. Vamos fazê-lo novamente com essas alterações.
EDIT: além de todas as linguagens possuírem sistemas de tempo de execução, a sobrecarga de iniciar cada um e interrompê-lo seria diferente. então sugiro que cronometramos nos sistemas de tempo de execução (para os idiomas aos quais isso se aplica). A Erlang VM é conhecida por ter uma sobrecarga considerável na inicialização!
fonte
A pergunta um pode ser respondida negativamente para Erlang. A última pergunta é respondida usando Erlang apropriadamente, como em:
http://bredsaal.dk/learning-erlang-using-projecteuler-net
Como é mais rápido que o seu exemplo inicial de C, eu acho que existem vários problemas, já que outros já abordaram em detalhes.
Este módulo Erlang é executado em um netbook barato em cerca de 5 segundos ... Ele usa o modelo de encadeamentos de rede em erlang e, como tal, demonstra como tirar proveito do modelo de evento. Pode ser distribuído por muitos nós. E é rápido. Não é o meu código.
O teste abaixo foi realizado em: CPU Intel (R) Atom (TM) N270 a 1,60GHz
fonte
C ++ 11, <20ms para mim - Execute-o aqui
Entendo que você deseja dicas para ajudar a aprimorar seu conhecimento específico do idioma, mas como isso foi bem abordado aqui, pensei em adicionar um contexto para as pessoas que possam ter olhado o comentário do mathematica sobre sua pergunta, etc., e me perguntado por que isso código era muito mais lento.
Esta resposta é principalmente para fornecer contexto e, espero, ajudar as pessoas a avaliar o código em sua pergunta / outras respostas mais facilmente.
Esse código usa apenas algumas otimizações (feias), não relacionadas ao idioma usado, com base em:
Isso leva em média 19ms no meu desktop e 80ms no meu laptop, muito longe da maioria dos outros códigos que eu já vi aqui. E há, sem dúvida, muitas otimizações ainda disponíveis.
fonte
Tentando GO:
Eu recebo:
versão original c: 9.1690 100%
go: 8.2520 111%
Mas usando:
Eu recebo:
versão c original: 9.1690 100%
versão c do thaumkid: 0.1060 8650%
versão go primeiro: 8.2520 111%
versão go segundo: 0.0230 39865%
Eu também tentei Python3.6 e pypy3.3-5.5-alpha:
versão c original: 8.629 100%
versão c de thaumkid: 0.109 7916%
Python3.6: 54.795 16%
pypy3.3-5.5-alpha: 13.291 65%
e então com o seguinte código eu obtive:
versão c original: 8.629 100%
versão c do thaumkid: 0.109 8650%
Python3.6: 1.489 580%
pypy3.3-5.5-alpha: 0.582 1483%
fonte
Mudança:
case (divisor(T,round(math:sqrt(T))) > 500) of
Para:
case (divisor(T,round(math:sqrt(T))) > 1000) of
Isso produzirá a resposta correta para o exemplo de multiprocessos Erlang.
fonte
Eu assumi que o número de fatores só é grande se os números envolvidos tiverem muitos fatores pequenos. Então, usei o excelente algoritmo de thaumkid, mas primeiro usei uma aproximação à contagem de fatores que nunca é muito pequena. É bem simples: verifique se há fatores primos até 29, depois verifique o número restante e calcule um limite superior para o número de fatores. Use isso para calcular um limite superior para o número de fatores e, se esse número for alto o suficiente, calcule o número exato de fatores.
O código abaixo não precisa dessa suposição para correção, mas para ser rápido. Parece funcionar; apenas cerca de um em 100.000 números fornece uma estimativa alta o suficiente para exigir uma verificação completa.
Aqui está o código:
Isso encontra o 14.753.024º triangular com 13824 fatores em cerca de 0,7 segundos, o 879.207.615º número triangular com 61.440 fatores em 34 segundos, o 12.524.486.975º número triangular com 138.240 fatores em 10 minutos e 5 segundos e o 26.467.792.064º número triangular com 172.032 fatores em 21 minutos e 25 segundos (2,4 GHz Core2 Duo), portanto, esse código leva em média apenas 116 ciclos de processador por número. O último número triangular em si é maior que 2 ^ 68, portanto
fonte
Modifiquei a versão "Jannich Brendle" para 1000 em vez de 500. E liste o resultado de euler12.bin, euler12.erl, p12dist.erl. Ambos os códigos erl usam '+ nativo' para compilar.
fonte
gcc -lm -Ofast euler.c
time ./a.out
2.79s sistema 0.00s usuário 99% da CPU 2.794 total
fonte