J'ai pris le problème # 12 de Project Euler comme un exercice de programmation et pour comparer mes implémentations (sûrement pas optimales) en C, Python, Erlang et Haskell. Afin d'obtenir des temps d'exécution plus élevés, je recherche le premier numéro de triangle avec plus de 1000 diviseurs au lieu de 500 comme indiqué dans le problème d'origine.
Le résultat est le suivant:
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
Python:
lorenzo@enzo:~/erlang$ time ./euler12.py
842161320
real 1m16.632s
user 1m16.370s
sys 0m0.250s
Python avec 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
Sommaire:
- C: 100%
- Python: 692% (118% avec PyPy)
- Erlang: 436% (135% grâce à RichardC)
- Haskell: 1421%
Je suppose que C a un gros avantage car il utilise long pour les calculs et non des entiers de longueur arbitraire comme les trois autres. De plus, il n'a pas besoin de charger un runtime en premier (les autres?).
Question 1:
Erlang, Python et Haskell perdent-ils de la vitesse en raison de l'utilisation d'entiers de longueur arbitraire ou pas tant que les valeurs sont inférieures à MAXINT
?
Question 2: Pourquoi Haskell est-il si lent? Existe-t-il un indicateur de compilation qui désactive les freins ou est-ce mon implémentation? (Ce dernier est tout à fait probable car Haskell est un livre avec sept sceaux pour moi.)
Question 3: pouvez-vous me donner quelques conseils pour optimiser ces implémentations sans changer la façon dont je détermine les facteurs? Optimisation de toute façon: plus agréable, plus rapide, plus "natif" de la langue.
ÉDITER:
Question 4: Mes implémentations fonctionnelles permettent-elles le LCO (optimisation du dernier appel, également appelé élimination de la récursivité de la queue) et évitent ainsi d'ajouter des trames inutiles à la pile d'appels?
J'ai vraiment essayé d'implémenter le même algorithme aussi similaire que possible dans les quatre langues, même si je dois admettre que mes connaissances en Haskell et Erlang sont très limitées.
Codes sources utilisés:
#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]
. Hourra!Réponses:
En utilisant
GHC 7.0.3
,gcc 4.4.6
,Linux 2.6.29
sur un x86_64 Core2 Duo (2.5GHz) la machine, la compilation en utilisantghc -O2 -fllvm -fforce-recomp
pour Haskell etgcc -O3 -lm
pour C.-O3
)-O2
drapeau)factorCount'
code n'est pas explicitement tapé et par défautInteger
(merci à Daniel d'avoir corrigé mon mauvais diagnostic ici!). Donner une signature de type explicite (ce qui est de toute façon une pratique standard) en utilisantInt
et le temps passe à 11,1 secondesfactorCount'
vous avez inutilement appeléfromIntegral
. Un correctif n'entraîne cependant aucun changement (le compilateur est intelligent, chanceux pour vous).mod
oùrem
est plus rapide et suffisant. Cela change le temps à 8,5 secondes .factorCount'
applique constamment deux arguments supplémentaires qui ne changent jamais (number
,sqrt
). Une transformation travailleur / wrapper nous donne:C'est vrai, 7,95 secondes . Constamment une demi - seconde plus rapide que la solution C . Sans le
-fllvm
drapeau que j'obtiens toujours8.182 seconds
, le backend NCG se porte bien dans ce cas également.Conclusion: Haskell est génial.
Code résultant
EDIT: Alors maintenant que nous avons exploré cela, permet de répondre aux questions
Dans Haskell, l'utilisation
Integer
est plus lente queInt
mais combien plus lente dépend des calculs effectués. Heureusement (pour les machines 64 bits)Int
est suffisant. Pour des raisons de portabilité, vous devriez probablement réécrire mon code à utiliserInt64
ouWord64
(C n'est pas le seul langage avec along
).C'est ce que j'ai répondu plus haut. La réponse a été
-O2
rem
nonmod
(une optimisation souvent oubliée) etOui, ce n'était pas le problème. Bon travail et heureux que vous y ayez pensé.
la source
rem
c'est en fait un sous-composant de l'mod
opération (ils ne sont pas les mêmes). Si vous regardez dans la bibliothèque GHC Base, vous voyez desmod
tests pour plusieurs conditions et ajustez le signe en conséquence. (voirmodInt#
enBase.lhs
)mod
parrem
après avoir lu cette réponse (hé, oups). Voir le lien pour mes timings, mais la version courte est "presque identique au C".factorCount'
était récursif de queue, j'aurais pensé que le compilateur pouvait repérer les paramètres supplémentaires non modifiés et optimiser la récursivité de queue uniquement pour les paramètres changeants (Haskell étant un langage pur après tout, cela devrait être facile). Quelqu'un pense que le compilateur pourrait le faire ou devrais-je revenir pour lire plus d'articles théoriques?Il y a quelques problèmes avec l'implémentation d'Erlang. Comme référence pour les éléments suivants, mon temps d'exécution mesuré pour votre programme Erlang non modifié était de 47,6 secondes, contre 12,7 secondes pour le code C.
La première chose à faire si vous souhaitez exécuter du code Erlang intensif en calcul est d'utiliser du code natif. La compilation avec
erlc +native euler12
a réduit le temps à 41,3 secondes. Il s'agit cependant d'une accélération beaucoup plus faible (seulement 15%) que celle attendue de la compilation native sur ce type de code, et le problème est votre utilisation de-compile(export_all)
. Ceci est utile pour l'expérimentation, mais le fait que toutes les fonctions soient potentiellement accessibles de l'extérieur rend le compilateur natif très conservateur. (L'émulateur BEAM normal n'est pas tellement affecté.) Le remplacement de cette déclaration par-export([solve/0]).
donne une bien meilleure accélération: 31,5 secondes (près de 35% de la ligne de base).Mais le code lui-même a un problème: pour chaque itération dans la boucle factorCount, vous effectuez ce test:
Le code C ne fait pas cela. En général, il peut être difficile de faire une comparaison équitable entre différentes implémentations du même code, et en particulier si l'algorithme est numérique, car vous devez être sûr qu'ils font réellement la même chose. Une légère erreur d'arrondi dans une implémentation due à un transtypage quelque part peut l'amener à effectuer beaucoup plus d'itérations que l'autre, même si les deux atteignent finalement le même résultat.
Pour éliminer cette source d'erreur possible (et se débarrasser du test supplémentaire à chaque itération), j'ai réécrit la fonction factorCount comme suit, étroitement modelée sur le code C:
Cette réécriture, non
export_all
, et compilation native, m'a donné le temps d'exécution suivant:ce qui n'est pas trop mal par rapport au code C:
considérant qu'Erlang n'est pas du tout orienté vers l'écriture de code numérique, étant seulement 50% plus lent que C sur un programme comme celui-ci, c'est plutôt bien.
Enfin, concernant vos questions:
Question 1: erlang, python et haskell perdent-ils de la vitesse en raison de l'utilisation d'entiers de longueur arbitraire ou pas tant que les valeurs sont inférieures à MAXINT?
Oui, un peu. Dans Erlang, il n'y a aucun moyen de dire "utiliser l'arithmétique 32/64 bits avec bouclage", donc à moins que le compilateur ne prouve certaines limites sur vos entiers (et ce n'est généralement pas le cas), il doit vérifier tous les calculs pour voir s'ils peuvent tenir dans un seul mot balisé ou s'il doit les transformer en bignums alloués en tas. Même si aucun bignum n'est jamais utilisé dans la pratique lors de l'exécution, ces vérifications devront être effectuées. D'un autre côté, cela signifie que vous savez que l'algorithme n'échouera jamais à cause d'un entourage entier inattendu si vous lui donnez soudainement des entrées plus grandes qu'auparavant.
Question 4: Mes implémentations fonctionnelles permettent-elles LCO et évitent donc d'ajouter des trames inutiles sur la pile d'appels?
Oui, votre code Erlang est correct en ce qui concerne l'optimisation du dernier appel.
la source
En ce qui concerne l'optimisation de Python, en plus d'utiliser PyPy (pour des accélérations assez impressionnantes sans modification de votre code), vous pouvez utiliser la chaîne d' outils de traduction de PyPy pour compiler une version compatible RPython, ou Cython pour construire un module d'extension, les deux de qui sont plus rapides que la version C dans mes tests, avec le module Cython presque deux fois plus rapide . Pour référence, j'inclus également les résultats de référence C et PyPy:
C (compilé avec
gcc -O3 -lm
)PyPy 1.5
RPython (en utilisant la dernière révision de PyPy,
c2f583445aee
)Cython 0,15
La version RPython a quelques changements clés. Pour traduire en un programme autonome, vous devez définir votre
target
, qui est dans ce cas lamain
fonction. Il est censé acceptersys.argv
comme seul argument et doit renvoyer un entier. Vous pouvez le traduire en utilisant translate.py,% translate.py euler12-rpython.py
qui se traduit en C et le compile pour vous.La version Cython a été réécrite en tant que module d'extension
_euler12.pyx
, que j'importe et appelle à partir d'un fichier python normal. Le_euler12.pyx
est essentiellement le même que votre version, avec quelques déclarations de type statique supplémentaires. Le setup.py a le passe-partout normal pour construire l'extension, en utilisantpython setup.py build_ext --inplace
.Honnêtement, j'ai très peu d'expérience avec RPython ou Cython, et j'ai été agréablement surpris des résultats. Si vous utilisez CPython, l'écriture de vos bits de code gourmands en CPU dans un module d'extension Cython semble être un moyen très simple d'optimiser votre programme.
la source
L'implémentation C est sous-optimale (comme le suggère Thomas M. DuBuisson), la version utilise des entiers 64 bits (c'est-à-dire un type de données long ). J'examinerai la liste des assembleurs plus tard, mais avec une supposition éclairée, il y a des accès à la mémoire dans le code compilé, ce qui rend l'utilisation des entiers 64 bits beaucoup plus lente. C'est cela ou le code généré (que ce soit le fait que vous pouvez tenir moins d'entiers 64 bits dans un registre SSE ou arrondir un double à un entier 64 bits est plus lent).
Voici le code modifié (remplacez simplement long par int et j'ai explicitement inclus factorCount, bien que je ne pense pas que cela soit nécessaire avec gcc -O3):
Running + timing ça donne:
Pour référence, l'implémentation de haskell par Thomas dans la réponse précédente donne:
Conclusion: ne rien enlever à ghc, c'est un excellent compilateur, mais gcc génère normalement du code plus rapide.
la source
2.5 seconds
tandis qu'une modification similaire au code Haskell (passage à Word32, ajout de pragma INLINE) entraîne un runtime de4.8 seconds
. Peut-être que quelque chose peut être fait (pas banalement, semble-t-il) - le résultat gcc est certainement impressionnant.Jetez un œil à ce blog . Au cours de la dernière année, il a résolu quelques-uns des problèmes de Project Euler à Haskell et Python, et il a généralement trouvé que Haskell était beaucoup plus rapide. Je pense qu'entre ces langues, cela a plus à voir avec votre aisance et votre style de codage.
En ce qui concerne la vitesse Python, vous utilisez la mauvaise implémentation! Essayez PyPy , et pour des choses comme ça, vous le trouverez beaucoup, beaucoup plus rapide.
la source
Votre implémentation Haskell pourrait être considérablement accélérée en utilisant certaines fonctions des packages Haskell. Dans ce cas, j'ai utilisé des nombres premiers, qui sont juste installés avec 'cabal install prime';)
Calendrier:
Votre programme d'origine:
Amélioration de la mise en œuvre
Comme vous pouvez le voir, celui-ci fonctionne en 38 millisecondes sur la même machine où la vôtre a fonctionné en 16 secondes :)
Commandes de compilation:
la source
Juste pour le fun. Voici une implémentation Haskell plus «native»:
En utilisant
ghc -O3
, cela fonctionne régulièrement en 0,55-0,58 secondes sur ma machine (1,73 GHz Core i7).Une fonction factorCount plus efficace pour la version C:
Changer les longs en pouces en principal, en utilisant
gcc -O3 -lm
, cela s'exécute régulièrement en 0,31-0,35 secondes.Les deux peuvent être exécutés encore plus rapidement si vous tirez parti du fait que le nième nombre de triangle = n * (n + 1) / 2, et n et (n + 1) ont des factorisations premières complètement disparates, donc le nombre de facteurs de chaque moitié peut être multiplié pour trouver le nombre de facteurs de l'ensemble. Le suivant:
réduira le temps d'exécution du code c à 0,17-0,19 secondes, et il peut gérer des recherches beaucoup plus importantes - plus de 10000 facteurs prennent environ 43 secondes sur ma machine. Je laisse une accélération de haskell similaire au lecteur intéressé.
la source
C'est peu probable. Je ne peux pas en dire beaucoup sur Erlang et Haskell (enfin, peut-être un peu sur Haskell ci-dessous) mais je peux signaler beaucoup d'autres goulots d'étranglement en Python. Chaque fois que le programme essaie d'exécuter une opération avec certaines valeurs en Python, il doit vérifier si les valeurs sont du type approprié et cela coûte un peu de temps. Votre
factorCount
fonction alloue simplement une liste àrange (1, isquare + 1)
différents moments, et l'malloc
allocation de mémoire à l' exécution est beaucoup plus lente que l'itération sur une plage avec un compteur comme vous le faites en C. Notamment, lefactorCount()
est appelée plusieurs fois et alloue donc beaucoup de listes. N'oublions pas non plus que Python est interprété et que l'interpréteur CPython n'a pas grand souci d'être optimisé.EDIT : oh, eh bien, je note que vous utilisez Python 3 donc
range()
ne renvoie pas une liste, mais un générateur. Dans ce cas, mon point sur l'allocation des listes est à moitié faux: la fonction alloue simplement desrange
objets, qui sont néanmoins inefficaces mais pas aussi inefficaces que l'allocation d'une liste avec beaucoup d'éléments.Utilisez-vous des câlins ? Hugs est un interprète considérablement lent. Si vous l'utilisez, vous pouvez peut-être passer un meilleur moment avec GHC - mais je ne fais que cogiter l'hypotèse, le genre de choses qu'un bon compilateur Haskell fait sous le capot est assez fascinant et bien au-delà de ma compréhension :)
Je dirais que vous jouez à un jeu drôle. La meilleure partie de la connaissance des différentes langues est de les utiliser de la manière la plus différente possible :) Mais je m'égare, je n'ai tout simplement aucune recommandation pour ce point. Désolé, j'espère que quelqu'un pourra vous aider dans ce cas :)
Pour autant que je m'en souvienne, il vous suffit de vous assurer que votre appel récursif est la dernière commande avant de renvoyer une valeur. En d'autres termes, une fonction comme celle ci-dessous pourrait utiliser une telle optimisation:
Cependant, vous n'auriez pas une telle optimisation si votre fonction était comme celle ci-dessous, car il y a une opération (multiplication) après l'appel récursif:
J'ai séparé les opérations dans certaines variables locales pour indiquer clairement quelles opérations sont exécutées. Cependant, le plus habituel est de voir ces fonctions comme ci-dessous, mais elles sont équivalentes pour le point que je fais:
Notez qu'il appartient au compilateur / interprète de décider s'il effectuera la récursivité de la queue. Par exemple, l'interpréteur Python ne le fait pas si je me souviens bien (j'ai utilisé Python dans mon exemple uniquement en raison de sa syntaxe fluide). Quoi qu'il en soit, si vous trouvez des choses étranges telles que des fonctions factorielles avec deux paramètres (et l'un des paramètres a des noms tels que
acc
,accumulator
etc.), vous savez maintenant pourquoi les gens le font :)la source
Avec Haskell, vous n'avez vraiment pas besoin de penser explicitement aux récursions.
Dans le code ci-dessus, j'ai remplacé les récursions explicites dans la réponse de @Thomas par des opérations de liste courantes. Le code fait toujours exactement la même chose sans nous soucier de la récursivité de la queue. Il s'exécute (~ 7.49s ) environ 6% plus lentement que la version dans la réponse de @Thomas (~ 7.04s ) sur ma machine avec GHC 7.6.2, tandis que la version C de @Raedwulf fonctionne ~ 3.15s . Il semble que le GHC s'est amélioré au cours de l'année.
PS. Je sais que c'est une vieille question, et je tombe dessus à partir de recherches Google (j'ai oublié ce que je cherchais, maintenant ...). Je voulais juste commenter la question sur LCO et exprimer mes sentiments à propos de Haskell en général. Je voulais commenter la première réponse, mais les commentaires n'autorisent pas les blocs de code.
la source
Quelques chiffres et explications supplémentaires pour la version C. Apparemment, personne ne l'a fait pendant toutes ces années. N'oubliez pas de voter pour cette réponse afin qu'elle puisse être visible et apprise par tout le monde.
Première étape: analyse comparative des programmes de l'auteur
Spécifications de l'ordinateur portable:
Commandes:
.
Les noms de fichiers sont:
integertype_architecture_compiler.exe
Deuxième étape: enquêter, améliorer et comparer à nouveau
VS est 250% plus rapide que gcc. Les deux compilateurs devraient donner une vitesse similaire. De toute évidence, quelque chose ne va pas avec le code ou les options du compilateur. Cherchons!
Le premier point d'intérêt est les types entiers. Les conversions peuvent être coûteuses et la cohérence est importante pour une meilleure génération de code et optimisations. Tous les entiers doivent être du même type.
C'est un désordre mixte de
int
et enlong
ce moment. Nous allons améliorer cela. Quel type utiliser? Le plus rapide. Je dois tous les comparer!Les types entiers sont
int
long
int32_t
uint32_t
int64_t
etuint64_t
de#include <stdint.h>
Il y a BEAUCOUP de types entiers en C, plus certains signés / non signés pour jouer avec, plus le choix de compiler en x86 ou x64 (à ne pas confondre avec la taille entière réelle). C'est beaucoup de versions à compiler et à exécuter ^^
Troisième étape: comprendre les chiffres
Conclusions définitives:
Question piège: "Quelles sont les tailles d'int et de long en C?"
La bonne réponse est: la taille de int et long en C ne sont pas bien définies!
De la spécification C:
Depuis la page de manuel de gcc (indicateurs -m32 et -m64):
De la documentation MSDN (plages de types de données) https://msdn.microsoft.com/en-us/library/s3f49ktz%28v=vs.110%29.aspx :
Pour conclure: leçons apprises
Les entiers 32 bits sont plus rapides que les entiers 64 bits.
Les types d'entiers standard ne sont pas bien définis en C ni en C ++, ils varient en fonction des compilateurs et des architectures. Lorsque vous avez besoin de cohérence et de prévisibilité, utilisez la
uint32_t
famille entière de#include <stdint.h>
.Problèmes de vitesse résolus. Toutes les autres langues sont de retour à des centaines de pour cent, C & C ++ gagne à nouveau! Ils le font toujours. La prochaine amélioration sera le multithreading en utilisant OpenMP: D
la source
INT_MIN
etINT_MAX
(-32767 et 32767, qui imposent pratiquement une exigence d'int
au moins 16 bits).long
doit être au moins aussi grand qu'unint
, et la moyenne des exigences de plagelong
est d'au moins 32 bits.En regardant votre implémentation Erlang. Le timing a inclus le démarrage de la machine virtuelle entière, l'exécution de votre programme et l'arrêt de la machine virtuelle. Suis assez sûr que la mise en place et l'arrêt du erlang vm prend un certain temps.
Si le chronométrage était effectué au sein de la machine virtuelle erlang elle-même, les résultats seraient différents car dans ce cas, nous n'aurions l'heure réelle que pour le programme en question. Sinon, je crois que le temps total pris par le processus de démarrage et de chargement de l'Erlang Vm plus celui de son arrêt (comme vous le mettez dans votre programme) sont tous inclus dans le temps total que la méthode que vous utilisez pour chronométrer le programme sort. Pensez à utiliser le timing erlang lui-même que nous utilisons lorsque nous voulons synchroniser nos programmes au sein de la machine virtuelle elle-même
timer:tc/1 or timer:tc/2 or timer:tc/3
. De cette façon, les résultats d'erlang excluront le temps nécessaire pour démarrer et arrêter / tuer / arrêter la machine virtuelle. C'est mon raisonnement là-bas, pensez-y, puis essayez à nouveau votre repère.Je suggère en fait que nous essayions de chronométrer le programme (pour les langues qui ont un runtime), dans le temps d'exécution de ces langues afin d'obtenir une valeur précise. C, par exemple, n'a pas de frais généraux pour démarrer et arrêter un système d'exécution comme Erlang, Python et Haskell (98% sûr de cela - je corrige). Donc (sur la base de ce raisonnement), je conclus en disant que ce benchmark n'était pas suffisamment précis / juste pour les langues fonctionnant au-dessus d'un système d'exécution. Permet de recommencer avec ces changements.
EDIT: en outre, même si toutes les langues avaient des systèmes d'exécution, la surcharge de démarrage et d'arrêt de chacun différerait. donc je suggère que nous chronométrions à partir des systèmes d'exécution (pour les langues pour lesquelles cela s'applique). La VM Erlang est connue pour avoir des frais généraux considérables au démarrage!
la source
La première question peut recevoir une réponse négative pour Erlang. On répond à la dernière question en utilisant Erlang de manière appropriée, comme dans:
http://bredsaal.dk/learning-erlang-using-projecteuler-net
Comme il est plus rapide que votre exemple C initial, je suppose qu'il existe de nombreux problèmes, car d'autres ont déjà été traités en détail.
Ce module Erlang s'exécute sur un netbook bon marché en environ 5 secondes ... Il utilise le modèle de threads réseau dans erlang et, en tant que tel, montre comment tirer parti du modèle d'événement. Il pourrait être distribué sur de nombreux nœuds. Et c'est rapide. Pas mon code.
Le test ci-dessous a eu lieu sur: un processeur Intel (R) Atom (TM) N270 à 1,60 GHz
la source
C ++ 11, <20 ms pour moi - Exécutez-le ici
Je comprends que vous vouliez des conseils pour améliorer vos connaissances spécifiques à la langue, mais comme cela a été bien couvert ici, j'ai pensé ajouter un peu de contexte pour les personnes qui auraient pu lire le commentaire mathématique sur votre question, etc., et je me suis demandé pourquoi cela le code était tellement plus lent.
Cette réponse est principalement destinée à fournir un contexte pour, espérons-le, aider les gens à évaluer le code dans votre question / autres réponses plus facilement.
Ce code utilise seulement quelques optimisations (laides), sans rapport avec le langage utilisé, basées sur:
Cela prend environ 19 ms en moyenne pour mon ordinateur de bureau et 80 ms pour mon ordinateur portable, loin de la plupart des autres codes que j'ai vus ici. Et il y a, sans aucun doute, de nombreuses optimisations encore disponibles.
la source
Essayer GO:
Je reçois:
version c originale: 9.1690 100%
go: 8.2520 111%
Mais en utilisant:
Je reçois:
version c d'origine: 9,1690 100%
version c de thaumkid: 0,1060 8650%
version premier passage: 8,2520 111%
version deuxième passage: 0,0230 39865%
J'ai également essayé Python3.6 et pypy3.3-5.5-alpha:
version c originale: 8.629 100%
version c de thaumkid: 0.109 7916%
Python3.6: 54.795 16%
pypy3.3-5.5-alpha: 13.291 65%
puis avec le code suivant, j'ai:
version c originale: 8.629 100%
version c de thaumkid: 0.109 8650%
Python3.6: 1.489 580%
pypy3.3-5.5-alpha: 0.582 1483%
la source
Changement:
case (divisor(T,round(math:sqrt(T))) > 500) of
À:
case (divisor(T,round(math:sqrt(T))) > 1000) of
Cela produira la bonne réponse pour l'exemple multi-processus d'Erlang.
la source
J'ai fait l'hypothèse que le nombre de facteurs n'est important que si les nombres impliqués ont beaucoup de petits facteurs. J'ai donc utilisé l'excellent algorithme de thaumkid, mais j'ai d'abord utilisé une approximation du nombre de facteurs qui n'est jamais trop petite. C'est assez simple: vérifiez les facteurs premiers jusqu'à 29, puis vérifiez le nombre restant et calculez une limite supérieure pour le nombre de facteurs. Utilisez-le pour calculer une limite supérieure pour le nombre de facteurs, et si ce nombre est suffisamment élevé, calculez le nombre exact de facteurs.
Le code ci-dessous n'a pas besoin de cette hypothèse pour être correct, mais pour être rapide. Cela semble fonctionner; seulement environ un chiffre sur 100 000 donne une estimation suffisamment élevée pour nécessiter une vérification complète.
Voici le code:
On trouve ainsi le 14 753 024ème triangulaire avec 13824 facteurs en environ 0,7 seconde, le 879 207 715ème nombre triangulaire avec 61 440 facteurs en 34 secondes, le 12 524 486 975ème nombre triangulaire avec 138 240 facteurs en 10 minutes 5 secondes et le 26 467 792 064ème nombre triangulaire avec 172 032 facteurs en 21 minutes 25 secondes (2,4 GHz Core2 Duo), ce code ne prend donc que 116 cycles de processeur par numéro en moyenne. Le dernier nombre triangulaire lui-même est supérieur à 2 ^ 68, donc
la source
J'ai modifié la version "Jannich Brendle" à 1000 au lieu de 500. Et liste le résultat de euler12.bin, euler12.erl, p12dist.erl. Les deux codes erl utilisent '+ native' pour compiler.
la source
gcc -lm -Ofast euler.c
time ./a.out
2.79s utilisateur 0.00s système 99% cpu 2.794 au total
la source