J'ai pris Problème n°12 de Projet 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 cherche le premier nombre triangulaire avec plus de 1000 diviseurs au lieu de 500 comme indiqué dans le problème original.
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
Résumé :
- C : 100%
- Python : 692% (118% avec PyPy)
- Erlang : 436% (135% grâce à RichardC)
- Haskell : 1421%
Je suppose que le C a un gros avantage car il utilise des longs 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 (comme les autres ?).
Question 1 : Erlang, Python et Haskell perdent-ils de la vitesse en raison de l'utilisation d'entiers de longueur arbitraire ou non tant que les valeurs sont inférieures à MAXINT
?
Question 2 : Pourquoi Haskell est-il si lent ? Y a-t-il un drapeau du compilateur qui désactive les freins ou est-ce mon implémentation ? (Cette dernière hypothèse est tout à fait probable car Haskell est pour moi un livre à sept sceaux).
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 quelque manière que ce soit : plus agréable, plus rapide, plus "native" au langage.
EDIT :
Question 4 : Mes implémentations fonctionnelles permettent-elles le LCO (last call optimization, c'est-à-dire l'élimination de la récursion de queue) et évitent-elles ainsi d'ajouter des trames inutiles sur la pile d'appels ?
J'ai vraiment essayé d'implémenter le même algorithme de manière aussi similaire que possible dans les quatre langages, 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
1 votes
Il existe un page web associée uniquement à la mise en évidence des différences de vitesse entre les langues. Sur la base de vos données par rapport à la page de tir, je dirais que votre algorithme n'est pas aussi bon que certains des autres utilisés dans la page de tir.
57 votes
@Jochen (et Seth) Ce n'est pas vraiment que le C est rapide ou génial, mais il est perçu comme facile d'écrire du code performant (ce n'est peut-être pas vrai, mais la plupart des programmes semblent en être capables, donc c'est assez vrai). Comme je l'explique dans ma réponse, et comme je l'ai constaté au fil du temps, les compétences du programmeur et sa connaissance des optimisations courantes pour le langage choisi sont d'une grande importance (surtout pour Haskell).
1 votes
Vous devriez être en mesure d'accélérer le code Python en plaçant le code principal au niveau du module (
triangle = 1
...print(triangle)
) en une fonction (main
) et ensuite faireif __name__ == "__main__": main()
à la fin. (Un peu comme votre code C.) Cela rendra les références aux variables locales beaucoup plus rapides.0 votes
Compte tenu de mes suggestions dans ma réponse à cette question, je demande que l'on refasse ce benchmark en prenant en considération mes raisons.
1 votes
"1742%" - Les pourcentages exagèrent les différences (ils font paraître les différences 100x plus grandes qu'elles ne le sont en réalité) et peuvent être assez déroutants. Au lieu de cela, dites simplement combien de fois plus lent.
0 votes
@Muzaaya J'ai pris en compte vos remarques et j'ai mesuré les programmes avec timer:tc et time.clock respectivement. Aucun changement réel. Le vrai frein était d'utiliser python3.2 au lieu de pypy et de ne pas compiler erlc avec +native. Malheureusement je n'ai pas pu reproduire les gains de vitesse de Thomas avec haskell. Mêmes flags de compilation, même code, et pas de changement au niveau du temps d'exécution sur ma machine.
0 votes
@igouy 1742% était une erreur de frappe. C'est 1421%. "Dites plutôt combien de fois plus lent". Il suffit d'ajouter un point décimal dans le nombre. 1421% = 14,21 fois plus lent. 100% = 1.
0 votes
@Hyperboreus - Pourquoi exagérer la différence en premier lieu ?
0 votes
@kizzx2 Optimisation du dernier appel. fr.wikipedia.org/wiki/Tail_call
5 votes
@Hyperboreus Oh c'est un nouveau nom pour moi pour la bonne vieille récursion de queue :P Je ne trouve aucune référence à "LCO" sur cette page mais de toute façon.
56 votes
Je viens de vérifier avec Mathematica -- cela prend 0.25sec (avec C cela prend 6sec ici), et le code est juste :
Euler12[x_Integer] := Module[{s = 1}, For[i = 2, DivisorSigma[0, s] < x, i++, s += i]; s]
. hourra !36 votes
Y a-t-il quelqu'un d'autre qui se souvient de ces guerres entre C et assemblage ? "Bien sûr ! Vous pouvez écrire votre code 10x plus vite en C, mais votre code C peut-il s'exécuter aussi vite ?..." Je suis sûr que les mêmes batailles ont été menées entre le code machine et l'assemblage.
0 votes
@JS Surement. Mais mon but était plutôt d'apprendre à tirer parti des différents langages afin de pouvoir appliquer les éléments appris dans le futur code de production.
43 votes
@JS : Probablement pas, car l'assemblage est simplement un ensemble de mnémoniques que vous tapez à la place du code machine binaire brut - normalement il y a une correspondance 1-1 entre eux.
2 votes
Je viens d'écrire une implémentation C++ qui a exécuté et produit la bonne réponse sous Windows en 212 ms. L'idée originale de l'OP était de comparer les temps d'exécution, mais j'allais faire un point sur la façon dont vous pouvez être intelligent en C++ et aligner le problème en mémoire d'une telle manière, qui s'exécuterait plus rapidement sur le métal que vous pourriez faire dans n'importe quel langage interprété. Je ne suis pas aussi bien versé dans Haskell, mais je ne crois pas que vous ayez autant de contrôle sur ce qui va dans quel cache quand.
1 votes
@Gleno : pourquoi ne partagez-vous pas votre code ?
0 votes
@Bruno, parce que mon code ne fait pas vraiment de choses intelligentes ! Et avoir une amélioration de plusieurs ordres de grandeur ET AJOUTER des bits intelligents, ne serait pas très bien testé.
0 votes
@j0ker5 Avec ce que je soupçonne être le même algorithme (celui du wiki Haskell, aussi concis), j'obtiens une solution qui s'exécute en
0.32
secondes (alors que le C d'Hyperboreus tourne en 8,5 secondes). Donc, comparable. J'admire les performances et les solutions concises de Mathematica, mais la dernière fois que j'ai regardé, il ne s'extrayait pas raisonnablement vers un autre langage (ou n'avait pas une implémentation ouverte, par ce qui est évident). La situation a-t-elle changé ?0 votes
@Thomas : Pas à ma connaissance, malheureusement.
15 votes
La conclusion, pour Haskell : -O2 lui donne un gain de vitesse d'environ 3x, et l'utilisation de Int au lieu de Integer environ 4x-6x pour un gain de vitesse total de 12x-14x et plus.
0 votes
Remarque : voici d'autres approches Haskell intéressantes du même problème - voir le point 5 et ses révisions. scrollingtext.org/projet-euler-problem-12
0 votes
Si vous vous retrouvez à écrire en Python plus lentement qu'en Haskell, c'est probablement dû à votre propre manque d'expérience avec Haskell. Ce n'est peut-être pas toujours plus rapide que C mais il s'en rapproche généralement.
0 votes
@user8174234 Vous voulez dire plus rapide ?
3 votes
Suis-je déraisonnable ou stupide en voulant supprimer les opérations d'impression de l'évaluation comparative des tâches/codes centrés sur le calcul mathématique ?