Outils pour l'analyse des performances d'un programme Haskell
Lors de la résolution de certains Projet Euler Problèmes pour apprendre Haskell (donc, actuellement, je suis totalement débutant) je suis venu sur Problème 12. J'ai écrit ce (naïve) solution:
--Get Number of Divisors of n
numDivs :: Integer -> Integer
numDivs n = toInteger $ length [ x | x<-[2.. ((n `quot` 2)+1)], n `rem` x == 0] + 2
--Generate a List of Triangular Values
triaList :: [Integer]
triaList = [foldr (+) 0 [1..n] | n <- [1..]]
--The same recursive
triaList2 = go 0 1
where go cs n = (cs+n):go (cs+n) (n+1)
--Finds the first triangular Value with more than n Divisors
sol :: Integer -> Integer
sol n = head $ filter (\x -> numDivs(x)>n) triaList2
Cette Solution pour n=500
(sol 500)
est extrêmement lent (plus de 2 heures maintenant), donc je me demandais comment savoir pourquoi cette solution est si lent. Existe-il des commandes dites-moi où la plupart des calculs de temps est passé donc je sais quelle partie de mon haskell-le programme est-il lent? Quelque chose comme un simple générateur de profils.
Pour que ce soit clair, je ne demande pas pour une solution plus rapide mais pour un moyen pour trouver cette solution. Comment vous y prendriez vous si vous n'avez pas le haskell connaissances?
J'ai essayé d'écrire deux triaList
fonctions mais n'a trouvé aucun moyen de tester celui qui est plus rapide, donc c'est là que mes problèmes de démarrage.
Grâce
Vous devez vous connecter pour publier un commentaire.
Précisément! GHC fournit beaucoup d'excellents outils, y compris:
Un tutoriel sur l'utilisation du temps et de l'espace de profilage est une partie de Real World Haskell.
GC Statistiques
Tout d'abord, assurez-vous compilez avec ghc -O2. Et vous pouvez assurez-vous qu'il est un moderne GHC (par exemple, GHC 6.12.x)
La première chose que nous pouvons faire est de vérifier que la collecte des ordures n'est pas le problème.
Exécuter votre programme avec +RTS-s
Qui nous donne déjà beaucoup d'informations: vous n'avez qu'à 2M tas, et la cg prend en hausse de 0,8% du temps. Donc pas besoin de s'inquiéter que l'allocation est le problème.
Des Profils De Temps
L'obtention d'un profil de temps pour votre programme est simple: le compiler avec -prof -auto-tous les
Et, pour N=200:
qui crée un fichier, A. prof, contenant:
Indiquant que tous votre temps est consacré au numDivs, et c'est aussi la source de toutes vos allocations.
Tas De Profils
Vous pouvez également obtenir une ventilation de ces allocations, en exécutant avec +RTS-p -hy, qui crée de l'A. hp, que vous pouvez consulter en le convertissant vers un fichier postscript (hp2ps -c A. ch), la génération:
qui nous dit il n'y a rien de mal avec votre utilisation de la mémoire: c'est l'allocation de la constante de l'espace.
Si votre problème est la complexité algorithmique de numDivs:
Correctif, qui est de 100% de votre temps de course, et tout le reste est facile.
Optimisations
Cette expression est un bon candidat pour la flux de fusion d'optimisation, donc je vais le réécrire
pour utiliser Les données.Vecteur, comme suit:
Qui devraient fusionner en une seule boucle avec pas de superflu allocations de tas. C'est, il aura une meilleure complexité (par des facteurs constants) que la version de liste. Vous pouvez utiliser le ghc-outils de base (pour les utilisateurs avancés) pour inspecter le code intermédiaire après l'optimisation.
De tester ce, ghc -O2 --faire Z. hs
De sorte qu'il réduit les temps d'exécution pour N=150 par 3,5 x, sans modification de l'algorithme lui-même.
Conclusion
Votre problème est numDivs. C'est 100% de votre temps de course, et a terrible complexité. Penser numDivs, et comment, par exemple, pour chaque N de la production [2 .. n
div
2 + 1] N fois.Essayez memoizing que, puisque les valeurs ne changent pas.
À mesure de vos fonctions est plus rapide, pensez à utiliser critère, qui fournira statistiquement robuste de l'information sur la sous-microseconde des améliorations dans la gestion du temps.
Addenda
Depuis numDivs est de 100% de votre temps de course, de toucher d'autres parties du programme ne fera pas beaucoup de différence,
toutefois, à des fins pédagogiques, on peut aussi réécrire ces à l'aide de flux de fusion.
On peut aussi réécrire trialList, et de s'appuyer sur la fusion pour en faire la boucle de vous écrire à la main dans trialList2,
qui est un "préfixe d'analyse de la fonction" (aka scanl):
De même pour le sol:
Avec le même temps de course total, mais un peu plus propre code.
time
utilitaire qui Ne sont mentionnés dans le Temps des Profils est juste le Linuxtime
programme. Il n'est pas disponible dans Windows . Pour le temps de profilage sur Windows (n'importe où en fait), voir this question.-auto-all
est déconseillée en faveur de-fprof-auto
.Enfile réponse est grand sans être un spoiler en donnant une solution directe au problème.
Ici je veux suggérer un peu de outil que j'ai écrit récemment. Il vous permet d'économiser le temps d'écrire CSC annotations à la main lorsque vous voulez un profil plus détaillé que celui par défaut
ghc -prof -auto-all
. Outre qu'il est coloré!Voici un exemple avec le code que vous avez donné(*), le vert est OK, le rouge est lente:
Tout le temps va dans la création de la liste des diviseurs. Ceci suggère quelques choses que vous pouvez faire:
1. Faire le filtrage
n rem x == 0
plus rapide, mais comme c'est une fonction intégrée probablement c'est déjà rapide.2. Créer une liste plus courte. Vous avez déjà fait quelque chose dans cette direction en vérifiant uniquement jusqu'à
n quot 2
.3. Jetez la liste génération complètement et l'utilisation de maths pour obtenir une solution plus rapide. C'est la manière habituelle pour projet Euler problèmes.
(*) Je l'ai obtenu en mettant votre code dans un fichier appelé
eu13.hs
, l'ajout d'une fonction principalemain = print $ sol 90
. Puis en exécutantvisual-prof -px eu13.hs eu13
et le résultat est danseu13.hs.html
.Haskell note:
triaList2
est bien sûr plus rapide quetriaList
parce que celui-ci effectue beaucoup de calculs inutiles. Il faudra quadratique du temps pour calculer les n premiers éléments detriaList
, mais linéaire pourtriaList2
. Il y a un autre élégant (et efficace) de manière à définir une infinie paresseux liste de triangle numéros:Mathématiques liée remarque: il n'est pas nécessaire de vérifier tous les diviseurs de n /2, il suffit de vérifier jusqu'à sqrt(n).
Vous pouvez exécuter votre programme avec des drapeaux pour activer le temps de profilage. Quelque chose comme ceci:
Que doit exécuter le programme et de produire un fichier appelé programme.des stats qui ont le temps passé dans chaque fonction. Vous pouvez trouver plus d'informations sur le profilage GHC dans le GHC guide de l'utilisateur. Pour l'analyse comparative, il y a le Critère de la bibliothèque. J'ai trouvé cette blog post est une introduction utile.
ghc -prof -auto-all -fforce-recomp --make -O2 program.hs