web-dev-qa-db-fra.com

Comparaison de la vitesse avec Project Euler: C vs Python vs Erlang vs Haskell

J'ai pris le problème n ° 12 de Project Euler comme 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 nombre de triangles avec plus de 1000 diviseurs au lieu de 500, comme indiqué dans le problème initial.

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 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’est pas nécessaire de charger un runtime d’abord (Est-ce que les autres?).

Question 1: Est-ce que Erlang, Python et Haskell perdent de la vitesse en raison de l'utilisation d'entiers de longueur arbitraire ou ne les ont-ils pas aussi longtemps que le 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 s'agit-il de mon implémentation? (Ce dernier est tout à fait probable, car Haskell est un livre avec sept sceaux pour moi.)

Question 3: Pouvez-vous me suggérer quelques astuces pour optimiser ces implémentations sans changer la façon dont je détermine les facteurs? L'optimisation à tous les niveaux: plus agréable, plus rapide, plus "natif" de la langue.

EDIT:

Question 4: Est-ce que mes implémentations fonctionnelles permettent LCO (optimisation du dernier appel, élimination de la récursion finale) et donc d'éviter l'ajout de trames inutiles sur la pile d'appels?

J'ai vraiment essayé d'implémenter le même algorithme le plus similaire possible dans les quatre langues, même si je dois admettre que mes connaissances en Haskell et Erlang sont très limitées.


Codes source 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
637
Hyperboreus

Utilisation de GHC 7.0.3, gcc 4.4.6, Linux 2.6.29 sur un ordinateur x86_64 Core2 Duo (2,5 GHz), compilation à l'aide de ghc -O2 -fllvm -fforce-recomp pour Haskell et gcc -O3 -lm pour C.

  • Votre routine C s'exécute en 8,4 secondes (plus rapide que votre exécution probablement à cause de -O3)
  • La solution Haskell s'exécute en 36 secondes (en raison de l'indicateur -O2).
  • Votre code factorCount' n'est pas explicitement saisi et par défaut Integer (merci à Daniel d'avoir corrigé mon diagnostic erroné ici!). Donner une signature de type explicite (qui est quand même une pratique courante) en utilisant Int et en changeant le temps en 11.1 secondes
  • dans factorCount' vous avez inutilement appelé fromIntegral. Un correctif n'apporte aucun changement cependant (le compilateur est intelligent, chanceux pour vous).
  • Vous avez utilisé modrem 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 worker/wrapper nous donne:
 $ time ./so
 842161320  

 real    0m7.954s  
 user    0m7.944s  
 sys     0m0.004s  

C'est vrai, 7,95 secondes. Constamment ne demi-seconde plus rapide que la solution C. Sans l'indicateur -fllvm, je reçois toujours 8.182 seconds, aussi le back-end NCG se porte-t-il bien dans ce cas également.

Conclusion: Haskell est génial.

code résultant

factorCount number = factorCount' number isquare 1 0 - (fromEnum $ square == fromIntegral isquare)
    where square = sqrt $ fromIntegral number
          isquare = floor square

factorCount' :: Int -> Int -> Int -> Int -> Int
factorCount' number sqrt candidate0 count0 = go candidate0 count0
  where
  go candidate count
    | candidate > sqrt = count
    | number `rem` candidate == 0 = go (candidate + 1) (count + 2)
    | otherwise = go (candidate + 1) count

nextTriangle index triangle
    | factorCount triangle > 1000 = triangle
    | otherwise = nextTriangle (index + 1) (triangle + index + 1)

main = print $ nextTriangle 1 1

EDIT: Alors maintenant que nous avons exploré cela, permet de répondre aux questions

Question 1: Est-ce que erlang, python et haskell perdent de la vitesse en raison de l'utilisation d'entiers de longueur arbitraire ou ne le sont-ils pas tant que les valeurs sont inférieures à MAXINT?

En Haskell, l'utilisation de Integer est plus lente que Int, mais de combien celle-ci dépend des calculs effectués. Heureusement (pour les machines 64 bits) Int est suffisant. Par souci de portabilité, vous devriez probablement réécrire mon code pour utiliser Int64 ou Word64 (C n'est pas la seule langue avec un long).

Question 2: Pourquoi haskell est-il si lent? Existe-t-il un indicateur de compilation qui désactive les freins ou s'agit-il de 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 astuces pour optimiser ces implémentations sans changer la façon dont je détermine les facteurs? L'optimisation à tous les niveaux: plus agréable, plus rapide, plus "natif" de la langue.

C'est ce que j'ai répondu ci-dessus. La réponse était

  • 0) Utiliser l'optimisation via -O2
  • 1) Utilisez des types rapides (notamment: unboxable) lorsque cela est possible
  • 2) rem pas mod (optimisation souvent oubliée) et
  • 3) transformation travailleur/wrapper (peut-être l'optimisation la plus courante).

Question 4: Mes implémentations fonctionnelles permettent-elles le LCO et évitent-elles donc d'ajouter des trames inutiles à la pile d'appels?

Oui, ce n'était pas le problème. Bon travail et heureux que vous ayez considéré cela.

771

La mise en œuvre d’Erlang pose quelques problèmes. En guise de base pour ce qui suit, le 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 exigeant en calculs intensifs consiste à utiliser du code natif. La compilation avec erlc +native euler12 réduit le temps à 41,3 secondes. Il s’agit toutefois d’une vitesse d’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). C'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 trop affecté.) Remplacer cette déclaration par -export([solve/0]). accélère considérablement: 31,5 secondes (près de 35% du niveau de référence).

Mais le code lui-même a un problème: pour chaque itération dans la boucle factorCount, vous effectuez ce test:

factorCount (_, Sqrt, Candidate, Count) when Candidate == Sqrt -> Count + 1;

Le code C ne fait pas cela. En général, il peut être difficile de faire une comparaison juste 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 à une conversion de type quelque part peut entraîner beaucoup plus d'itérations que l'autre, même si les deux aboutissent finalement au même résultat.

Pour éliminer cette source d'erreur possible (et supprimer le test supplémentaire à chaque itération), j'ai récrit la fonction factorCount comme suit, étroitement inspirée du code C:

factorCount (N) ->
    Sqrt = math:sqrt (N),
    ISqrt = trunc(Sqrt),
    if ISqrt == Sqrt -> factorCount (N, ISqrt, 1, -1);
       true          -> factorCount (N, ISqrt, 1, 0)
    end.

factorCount (_N, ISqrt, Candidate, Count) when Candidate > ISqrt -> Count;
factorCount ( N, ISqrt, Candidate, Count) ->
    case N rem Candidate of
        0 -> factorCount (N, ISqrt, Candidate + 1, Count + 2);
        _ -> factorCount (N, ISqrt, Candidate + 1, Count)
    end.

Cette réécriture, pas de export_all, et la compilation native, m'ont donné le temps d'exécution suivant:

$ erlc +native euler12.erl
$ time erl -noshell -s euler12 solve
842161320

real    0m19.468s
user    0m19.450s
sys 0m0.010s

ce qui n'est pas trop mal comparé au code C:

$ time ./a.out 
842161320

real    0m12.755s
user    0m12.730s
sys 0m0.020s

considérant qu'Erlang n'est pas du tout orienté vers l'écriture de code numérique, il est plutôt bon de n'être que 50% plus lent que C dans un programme comme celui-ci.

Enfin, concernant vos questions:

Question 1: erlang, python et haskell perdent-ils leur vitesse en raison de l'utilisation d'entiers de longueur arbitraire ou ne le sont-ils pas tant que les valeurs sont inférieures à MAXINT?

Oui, un peu. En Erlang, il n’existe aucun moyen de dire "utilisez l’arithmétique 32/64 bits avec un bouclage", donc à moins que le compilateur ne prouve certaines limites sur vos entiers (et il ne peut généralement pas), 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 au tas. Même si aucun bignum n'est utilisé dans la pratique au moment de l'exécution, ces vérifications devront être effectuées. D'autre part, cela signifie que vous savez que l'algorithme n'échouera jamais à cause d'un enveloppement entier inattendu si vous lui donnez soudainement des entrées plus volumineuses qu'auparavant.

Question 4: Mes implémentations fonctionnelles permettent-elles LCO et évitent-elles donc d'ajouter des trames inutiles à la pile d'appels?

Oui, votre code Erlang est correct en ce qui concerne l'optimisation du dernier appel.

217
RichardC

En ce qui concerne l'optimisation Python, en plus d'utiliser PyPy (pour des accélérations assez impressionnantes sans changement de code), vous pouvez utiliser la chaîne chaîne d'outils de traduction de PyPy pour compiler un RPython- version compatible, ou Cython pour construire un module d’extension, les deux étant plus rapides que la version C testée, avec le module Cython presque deux fois plus vite . Pour référence, j'inclus également les résultats de référence C et PyPy:

C (compilé avec gcc -O3 -lm)

% time ./euler12-c 
842161320

./euler12-c  11.95s 
 user 0.00s 
 system 99% 
 cpu 11.959 total

PyPy 1.5

% time pypy euler12.py
842161320
pypy euler12.py  
16.44s user 
0.01s system 
99% cpu 16.449 total

RPython (utilisant la dernière révision PyPy, c2f583445aee)

% time ./euler12-rpython-c
842161320
./euler12-rpy-c  
10.54s user 0.00s 
system 99% 
cpu 10.540 total

Cython 0.15

% time python euler12-cython.py
842161320
python euler12-cython.py  
6.27s user 0.00s 
system 99% 
cpu 6.274 total

La version RPython comporte quelques modifications clés. Pour traduire en programme autonome, vous devez définir votre target, qui dans ce cas est la fonction main. Il est prévu d'accepter sys.argv en tant qu'argument, et est requis pour renvoyer un int. Vous pouvez le traduire en utilisant translate.py, % translate.py euler12-rpython.py, qui traduit en C et le compile pour vous.

# euler12-rpython.py

import math, sys

def factorCount(n):
    square = math.sqrt(n)
    isquare = int(square)
    count = -1 if isquare == square else 0
    for candidate in xrange(1, isquare + 1):
        if not n % candidate: count += 2
    return count

def main(argv):
    triangle = 1
    index = 1
    while factorCount(triangle) < 1001:
        index += 1
        triangle += index
    print triangle
    return 0

if __== '__main__':
    main(sys.argv)

def target(*args):
    return main, None

La version de Cython a été réécrite en tant que module d'extension _euler12.pyx, que j'importe et appelle à partir d'un fichier normal python. Le _euler12.pyx est essentiellement identique à votre version, avec quelques déclarations de types statiques supplémentaires. Le fichier setup.py a le standard habituel pour construire l’extension, en utilisant python setup.py build_ext --inplace.

# _euler12.pyx
from libc.math cimport sqrt

cdef int factorCount(int n):
    cdef int candidate, isquare, count
    cdef double square
    square = 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

cpdef main():
    cdef int triangle = 1, index = 1
    while factorCount(triangle) < 1001:
        index += 1
        triangle += index
    print triangle

# euler12-cython.py
import _euler12
_euler12.main()

# setup.py
from distutils.core import setup
from distutils.extension import Extension
from Cython.Distutils import build_ext

ext_modules = [Extension("_euler12", ["_euler12.pyx"])]

setup(
  name = 'Euler12-Cython',
  cmdclass = {'build_ext': build_ext},
  ext_modules = ext_modules
)

Honnêtement, j'ai très peu d'expérience avec RPython ou Cython, et j'ai été agréablement surpris par les résultats. Si vous utilisez CPython, l'écriture de vos morceaux de code gourmands en ressources CPU dans un module d'extension Cython semble être un moyen très simple d'optimiser votre programme.

155
zeekay

Question 3: Pouvez-vous me donner quelques astuces pour optimiser ces implémentations sans changer la façon dont je détermine les facteurs? L'optimisation à tous les niveaux: plus agréable, plus rapide, plus "natif" de la langue.

L’implémentation C est sous-optimale (comme l’a laissé entendre Thomas M. DuBuisson), la version utilise des entiers 64 bits (c'est-à-dire long type de données). J'examinerai la liste d'assemblées plus tard, mais avec une hypothèse éclairée, le code compilé contient des accès à la mémoire, ce qui ralentit considérablement l'utilisation des entiers 64 bits. C’est cela ou le code généré (le fait que vous puissiez adapter moins d’inits 64 bits dans un registre SSE ou arrondir un double à un entier 64 bits est plus lent).

Voici le code modifié (il suffit de remplacer long par int et explicitement factorCount en ligne, bien que je ne pense pas que cela soit nécessaire avec gcc -O3):

#include <stdio.h>
#include <math.h>

static inline int factorCount(int n)
{
    double square = sqrt (n);
    int isquare = (int)square;
    int count = isquare == square ? -1 : 0;
    int candidate;
    for (candidate = 1; candidate <= isquare; candidate ++)
        if (0 == n % candidate) count += 2;
    return count;
}

int main ()
{
    int triangle = 1;
    int index = 1;
    while (factorCount (triangle) < 1001)
    {
        index++;
        triangle += index;
    }
    printf ("%d\n", triangle);
}

Running + timing ça donne:

$ gcc -O3 -lm -o euler12 euler12.c; time ./euler12
842161320
./euler12  2.95s user 0.00s system 99% cpu 2.956 total

Pour référence, l'implémentation de haskell par Thomas dans la réponse précédente donne:

$ ghc -O2 -fllvm -fforce-recomp euler12.hs; time ./euler12                                                                                      [9:40]
[1 of 1] Compiling Main             ( euler12.hs, euler12.o )
Linking euler12 ...
842161320
./euler12  9.43s user 0.13s system 99% cpu 9.602 total

Conclusion: Ne rien enlever à ghc, c’est un excellent compilateur, mais gcc génère normalement du code plus rapide.

70
Raedwulf

Jetez un oeil à ce blog . Au cours de la dernière année environ, il a fait quelques-uns des problèmes du projet Euler dans Haskell et Python, et il a généralement constaté 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 Python speed, vous utilisez la mauvaise implémentation! Essayez PyPy , et pour des choses comme celle-ci, vous constaterez que c'est beaucoup, beaucoup plus rapide.

56
agf

Votre implémentation Haskell pourrait être grandement accélérée en utilisant certaines fonctions des packages Haskell. Dans ce cas, j'ai utilisé prime, qui vient d'être installé avec 'cabal install prime';)

import Data.Numbers.Primes
import Data.List

triangleNumbers = scanl1 (+) [1..]
nDivisors n = product $ map ((+1) . length) (group (primeFactors n))
answer = head $ filter ((> 500) . nDivisors) triangleNumbers

main :: IO ()
main = putStrLn $ "First triangle number to have over 500 divisors: " ++ (show answer)

Horaires:

Votre programme original:

PS> measure-command { bin\012_slow.exe }

TotalSeconds      : 16.3807409
TotalMilliseconds : 16380.7409

Mise en œuvre améliorée

PS> measure-command { bin\012.exe }

TotalSeconds      : 0.0383436
TotalMilliseconds : 38.3436

Comme vous pouvez le constater, celui-ci s'exécute en 38 millisecondes sur la même machine que la vôtre en 16 secondes :)

Commandes de compilation:

ghc -O2 012.hs -o bin\012.exe
ghc -O2 012_slow.hs -o bin\012_slow.exe
31
Connie Hilarides

Juste pour le fun. Voici une implémentation Haskell plus "native":

import Control.Applicative
import Control.Monad
import Data.Either
import Math.NumberTheory.Powers.Squares

isInt :: RealFrac c => c -> Bool
isInt = (==) <$> id <*> fromInteger . round

intSqrt :: (Integral a) => a -> Int
--intSqrt = fromIntegral . floor . sqrt . fromIntegral
intSqrt = fromIntegral . integerSquareRoot'

factorize :: Int -> [Int]
factorize 1 = []
factorize n = first : factorize (quot n first)
  where first = (!! 0) $ [a | a <- [2..intSqrt n], rem n a == 0] ++ [n]

factorize2 :: Int -> [(Int,Int)]
factorize2 = foldl (\ls@((val,freq):xs) y -> if val == y then (val,freq+1):xs else (y,1):ls) [(0,0)] . factorize

numDivisors :: Int -> Int
numDivisors = foldl (\acc (_,y) -> acc * (y+1)) 1 <$> factorize2

nextTriangleNumber :: (Int,Int) -> (Int,Int)
nextTriangleNumber (n,acc) = (n+1,acc+n+1)

forward :: Int -> (Int, Int) -> Either (Int, Int) (Int, Int)
forward k val@(n,acc) = if numDivisors acc > k then Left val else Right (nextTriangleNumber val)

problem12 :: Int -> (Int, Int)
problem12 n = (!!0) . lefts . scanl (>>=) (forward n (1,1)) . repeat . forward $ n

main = do
  let (n,val) = problem12 1000
  print val

En utilisant ghc -O3, il s’exécute régulièrement en 0,55 à 0,58 seconde sur ma machine (Core i7 à 1,73 GHz).

Une fonction factorCount plus efficace pour la version C:

int factorCount (int n)
{
  int count = 1;
  int candidate,tmpCount;
  while (n % 2 == 0) {
    count++;
    n /= 2;
  }
    for (candidate = 3; candidate < n && candidate * candidate < n; candidate += 2)
    if (n % candidate == 0) {
      tmpCount = 1;
      do {
        tmpCount++;
        n /= candidate;
      } while (n % candidate == 0);
       count*=tmpCount;
      }
  if (n > 1)
    count *= 2;
  return count;
}

En changeant les longueurs en entrées dans main, en utilisant gcc -O3 -lm, cela fonctionne de manière constante en 0,31 à 0,35 seconde.

Les deux peuvent être conçus pour fonctionner encore plus rapidement si vous tirez parti du fait que le n-ème nombre du triangle = n * (n + 1)/2, et que n et (n + 1) ont des factorisations premières complètement disparates, ainsi le nombre de facteurs de chaque moitié peut être multiplié pour trouver le nombre de facteurs de l’ensemble. Le suivant:

int main ()
{
  int triangle = 0,count1,count2 = 1;
  do {
    count1 = count2;
    count2 = ++triangle % 2 == 0 ? factorCount(triangle+1) : factorCount((triangle+1)/2);
  } while (count1*count2 < 1001);
  printf ("%lld\n", ((long long)triangle)*(triangle+1)/2);
}

réduira le temps d’exécution du code c à 0,17-0,19 secondes et il peut gérer des recherches beaucoup plus volumineuses - plus de 10 000 facteurs prennent environ 43 secondes sur ma machine. Je laisse une accélération de haskell similaire au lecteur intéressé.

28
thaumkid
Question 1: Est-ce que erlang, python et haskell perdent leur vitesse en raison de l'utilisation d'entiers de longueur arbitraire ou ne le sont-ils pas tant que les valeurs sont inférieures à MAXINT?

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 tente d'exécuter une opération avec des valeurs en Python, il doit vérifier si les valeurs proviennent du type approprié et cela prend un peu de temps. Votre fonction factorCount alloue simplement une liste avec range (1, isquare + 1) plusieurs fois, et l'exécution, malloc- l'allocation de mémoire de style est bien plus lente que l'itération sur une plage avec un compteur comme en C. Notamment, le factorCount() est appelé plusieurs fois et alloue donc beaucoup de listes. De plus, n'oublions pas que Python est interprété et que l'interpréteur CPython n'a pas vraiment pour objectif d'être optimisé.

EDIT: oh, eh bien, je remarque que vous utilisez Python 3, donc range() ne renvoie pas de liste, mais un Générateur. Dans ce cas, mon argument sur l'allocation de listes est à moitié faux: la fonction n'alloue que des objets range, qui sont néanmoins inefficaces, mais pas aussi inefficaces que l'attribution d'une liste avec un grand nombre d'éléments.

Question 2: Pourquoi haskell est-il si lent? Existe-t-il un indicateur de compilation qui désactive les freins ou s'agit-il de mon implémentation? (Ce dernier est tout à fait probable car haskell est un livre avec sept sceaux pour moi.)

Utilisez-vous Hugs ? Hugs est un interprète considérablement lent. Si vous l'utilisez, vous obtiendrez peut-être un meilleur temps avec GHC - mais je ne fais que cogiter dans l'hypotesis, le genre de choses qu'un bon compilateur Haskell fait sous le capot est assez fascinant et va bien au-delà ma compréhension :)

Question 3: Pouvez-vous me donner quelques astuces pour optimiser ces implémentations sans changer la façon dont je détermine les facteurs? L'optimisation à tous les niveaux: plus agréable, plus rapide, plus "natif" de la langue.

Je dirais que vous jouez un jeu peu drôle. La meilleure partie de la connaissance de différentes langues est de les utiliser de la manière la plus différente possible :) Mais je m'éloigne du sujet, je n'ai aucune recommandation à cet égard. Désolé, j'espère que quelqu'un pourra vous aider dans ce cas :)

Question 4: Mes implémentations fonctionnelles permettent-elles le LCO et évitent-elles donc d'ajouter des trames inutiles à la pile d'appels?

Autant que je me souvienne, vous devez simplement 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:

def factorial(n, acc=1):
    if n > 1:
        acc = acc * n
        n = n - 1
        return factorial(n, acc)
    else:
        return acc

Cependant, vous n'auriez pas cette optimisation si votre fonction était telle que celle ci-dessous, car il y a une opération (multiplication) après l'appel récursif:

def factorial2(n):
    if n > 1:
        f = factorial2(n-1)
        return f*n
    else:
        return 1

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:

def factorial(n, acc=1):
    if n > 1:
        return factorial(n-1, acc*n)
    else:
        return acc

def factorial2(n):
    if n > 1:
        return n*factorial(n-1)
    else:
        return 1

Notez que c'est au compilateur/interprète de décider s'il fera la récursion finale. Par exemple, l'interprète Python ne le fait pas si je me souviens bien (j'ai utilisé Python dans mon exemple uniquement à cause de sa syntaxe courante). Quoi qu'il en soit, si vous trouvez des éléments étranges tels que des fonctions factorielles avec deux paramètres (et que l'un des paramètres porte des noms tels que acc, accumulator etc.), vous savez maintenant pourquoi les gens le font :)

13
brandizzi

Avec Haskell, vous n'avez vraiment pas besoin de penser explicitement aux récursions.

factorCount number = foldr factorCount' 0 [1..isquare] -
                     (fromEnum $ square == fromIntegral isquare)
    where
      square = sqrt $ fromIntegral number
      isquare = floor square
      factorCount' candidate
        | number `rem` candidate == 0 = (2 +)
        | otherwise = id

triangles :: [Int]
triangles = scanl1 (+) [1,2..]

main = print . head $ dropWhile ((< 1001) . factorCount) triangles

Dans le code ci-dessus, j'ai remplacé les récursions explicites dans la réponse de @Thomas par des opérations de liste communes. Le code fait toujours exactement la même chose sans nous inquiéter de la récursion de la queue. Il fonctionne (~ 7.49s) environ 6% plus lent que la version dans la réponse de @Thomas (~ 7.04s) sur ma machine avec GHC 7.6.2, alors que la version C de @Raedwulf est exécutée ~ .15s. Il semble que GHC s'est amélioré au cours de l'année.

PS Je sais que c’est une vieille question et j’ai trébuché à partir de recherches sur 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.

12
jxy

Quelques chiffres et explications supplémentaires pour la version C. Apparemment, personne ne l'a fait pendant toutes ces années. Rappelez-vous d’augmenter la réponse de cette réponse pour que tout le monde puisse la voir et l’apprendre.

Première étape: Benchmark des programmes de l'auteur

Spécifications de l'ordinateur portable:

  • CPU i3 M380 (931 MHz - mode économie de batterie maximale)
  • 4 Go de mémoire
  • Win7 64 bits
  • Microsoft Visual Studio 2012 Ultimate
  • Cygwin avec gcc 4.9.3
  • Python 2.7.10

Commandes:

compiling on VS x64 command Prompt > `for /f %f in ('dir /b *.c') do cl /O2 /Ot /Ox %f -o %f_x64_vs2012.exe`
compiling on cygwin with gcc x64   > `for f in ./*.c; do gcc -m64 -O3 $f -o ${f}_x64_gcc.exe ; done`
time (unix tools) using cygwin > `for f in ./*.exe; do  echo "----------"; echo $f ; time $f ; done`

.

----------
$ time python ./original.py

real    2m17.748s
user    2m15.783s
sys     0m0.093s
----------
$ time ./original_x86_vs2012.exe

real    0m8.377s
user    0m0.015s
sys     0m0.000s
----------
$ time ./original_x64_vs2012.exe

real    0m8.408s
user    0m0.000s
sys     0m0.015s
----------
$ time ./original_x64_gcc.exe

real    0m20.951s
user    0m20.732s
sys     0m0.030s

Les noms de fichiers sont: integertype_architecture_compiler.exe

  • integertype est identique au programme original pour le moment (pour plus d'informations à ce sujet plus tard)
  • architecture est x86 ou x64 en fonction des paramètres du compilateur
  • compilateur est gcc ou vs2012

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. Enquêtons!

Le premier point d’intérêt concerne les types entiers. Les conversions peuvent être coûteuses et la cohérence est importante pour une meilleure génération et optimisation de code. Tous les entiers doivent être du même type.

C'est un désordre mélangé de int et long en ce moment. Nous allons améliorer cela. Quel type utiliser? Le plus rapide. Je dois les comparer!

----------
$ time ./int_x86_vs2012.exe

real    0m8.440s
user    0m0.016s
sys     0m0.015s
----------
$ time ./int_x64_vs2012.exe

real    0m8.408s
user    0m0.016s
sys     0m0.015s
----------
$ time ./int32_x86_vs2012.exe

real    0m8.408s
user    0m0.000s
sys     0m0.015s
----------
$ time ./int32_x64_vs2012.exe

real    0m8.362s
user    0m0.000s
sys     0m0.015s
----------
$ time ./int64_x86_vs2012.exe

real    0m18.112s
user    0m0.000s
sys     0m0.015s
----------
$ time ./int64_x64_vs2012.exe

real    0m18.611s
user    0m0.000s
sys     0m0.015s
----------
$ time ./long_x86_vs2012.exe

real    0m8.393s
user    0m0.015s
sys     0m0.000s
----------
$ time ./long_x64_vs2012.exe

real    0m8.440s
user    0m0.000s
sys     0m0.015s
----------
$ time ./uint32_x86_vs2012.exe

real    0m8.362s
user    0m0.000s
sys     0m0.015s
----------
$ time ./uint32_x64_vs2012.exe

real    0m8.393s
user    0m0.015s
sys     0m0.015s
----------
$ time ./uint64_x86_vs2012.exe

real    0m15.428s
user    0m0.000s
sys     0m0.015s
----------
$ time ./uint64_x64_vs2012.exe

real    0m15.725s
user    0m0.015s
sys     0m0.015s
----------
$ time ./int_x64_gcc.exe

real    0m8.531s
user    0m8.329s
sys     0m0.015s
----------
$ time ./int32_x64_gcc.exe

real    0m8.471s
user    0m8.345s
sys     0m0.000s
----------
$ time ./int64_x64_gcc.exe

real    0m20.264s
user    0m20.186s
sys     0m0.015s
----------
$ time ./long_x64_gcc.exe

real    0m20.935s
user    0m20.809s
sys     0m0.015s
----------
$ time ./uint32_x64_gcc.exe

real    0m8.393s
user    0m8.346s
sys     0m0.015s
----------
$ time ./uint64_x64_gcc.exe

real    0m16.973s
user    0m16.879s
sys     0m0.030s

Les types entiers sont intlongint32_tuint32_tint64_t et uint64_t à partir de #include <stdint.h>

Il y a BEAUCOUP de types entiers en C, ainsi que certains signés/non signés avec lesquels jouer, plus le choix de compiler en tant que x86 ou x64 (à ne pas confondre avec la taille entière réelle). Cela fait beaucoup de versions pour compiler et exécuter ^^

Troisième étape: comprendre les chiffres

Conclusions définitives:

  • Les entiers 32 bits sont environ 200% plus rapides que les équivalents 64 bits
  • 64 bits non signés entiers sont 25% plus rapides que signés 64 bits (Malheureusement , Je n'ai aucune explication pour ça)

Question piège: "Quelles sont les tailles de int et long en C?"
La bonne réponse est: La taille de int et long en C ne sont pas bien définies!

À partir de la spécification C:

int est au moins 32 bits
long est au moins un int

Depuis la page de manuel de gcc (drapeaux -m32 et -m64):

L'environnement 32 bits définit int, long et pointeur sur 32 bits et génère un code qui s'exécute sur n'importe quel système i386.
L’environnement 64 bits définit int sur 32 bits et long et le pointeur sur 64 bits et génère un code pour l’architecture x86-64 d’AMD.

D'après la documentation MSDN (plages de types de données) https://msdn.Microsoft.com/en-us/library/s3f49ktz%28v=vs.110%29.aspx =:

int, 4 octets, sait aussi comme signé
long, 4 octets, sait aussi que long int et signé long int

Pour conclure ceci: 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 famille entière uint32_t de #include <stdint.h>.

  • Problèmes de vitesse résolus. Toutes les autres langues ont des centaines de retard, C & C++ gagne encore! Ils font toujours. La prochaine amélioration sera le multithreading avec OpenMP: D

10
user5994461

En regardant votre implémentation Erlang. Le minutage 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. Je suis à peu près sûr que la configuration et l'arrêt de l'erlang prennent un certain temps.

Si le minutage était effectué dans la machine virtuelle erlang elle-même, les résultats seraient différents car dans ce cas, nous aurions le temps réel uniquement pour le programme en question. Sinon, je crois que le temps total nécessaire au processus de démarrage et de chargement de l’Erlang Vm, plus celui de l’arrêter (comme vous le mettez dans votre programme), sont tous compris dans le temps total que la méthode que vous utilisez pour chronométrer programme est en sortie. Pensez à utiliser le timing erlang lui-même que nous utilisons lorsque nous voulons chronométrer nos programmes dans la machine virtuelle elle-même timer:tc/1 or timer:tc/2 or timer:tc/3 . De cette manière, les résultats de erlang excluent le temps nécessaire pour démarrer et arrêter/tuer/arrêter la machine virtuelle. C’est là mon raisonnement, réfléchissez-y, puis essayez à nouveau votre repère.

En fait, je suggère que nous essayions de chronométrer le programme (pour les langues qui ont une exécution), dans l'exécution de ces langues afin d'obtenir une valeur précise. C, par exemple, n’a aucun coût supplémentaire lié au démarrage et à l’arrêt d’un système d’exécution, tout comme Erlang, Python et Haskell (sûr à 98% - je résiste). Donc (sur la base de ce raisonnement), je conclus en disant que ce repère n'était pas assez précis/juste pour les langues s'exécutant sur un système d'exécution. Permet de le refaire avec ces changements.

EDIT: outre que même si toutes les langues avaient des systèmes d’exécution, la charge de démarrage et d’arrêt de chaque langue serait différente. Je suggère donc que nous travaillions à partir des systèmes d’exécution (pour les langues pour lesquelles cela s’applique). Erlang VM est connu pour avoir des frais généraux considérables au démarrage!

8
Muzaaya Joshua

Question 1: Erlang, Python et Haskell perdent-ils de la vitesse en raison de l'utilisation d'entiers de longueur arbitraire ou ne le sont-ils pas tant que les valeurs sont inférieures à MAXINT?

La première question peut recevoir une réponse négative pour Erlang. Pour répondre à la dernière question, utilisez Erlang de manière appropriée, comme dans:

http://bredsaal.dk/learning-erlang-using-projecteuler-net

Comme il est plus rapide que votre exemple initial C, je suppose qu’il existe de nombreux problèmes, d’autres ayant déjà été abordés en détail.

Ce module Erlang s'exécute sur un netbook pas cher en environ 5 secondes ... Il utilise le modèle de threads de réseau in erlang et, en tant que tel, montre comment tirer parti du modèle d'événement. Il pourrait être distribué sur plusieurs nœuds. Et c'est rapide. Ce n'est pas mon code.

-module(p12dist).  
-author("Jannich Brendle, [email protected], http://blog.bredsaal.dk").  
-compile(export_all).

server() ->  
  server(1).

server(Number) ->  
  receive {getwork, Worker_PID} -> Worker_PID ! {work,Number,Number+100},  
  server(Number+101);  
  {result,T} -> io:format("The result is: \~w.\~n", [T]);  
  _ -> server(Number)  
  end.

worker(Server_PID) ->  
  Server_PID ! {getwork, self()},  
  receive {work,Start,End} -> solve(Start,End,Server_PID)  
  end,  
  worker(Server_PID).

start() ->  
  Server_PID = spawn(p12dist, server, []),  
  spawn(p12dist, worker, [Server_PID]),  
  spawn(p12dist, worker, [Server_PID]),  
  spawn(p12dist, worker, [Server_PID]),  
  spawn(p12dist, worker, [Server_PID]).

solve(N,End,_) when N =:= End -> no_solution;

solve(N,End,Server_PID) ->  
  T=round(N*(N+1)/2),
  case (divisor(T,round(math:sqrt(T))) > 500) of  
    true ->  
      Server_PID ! {result,T};  
    false ->  
      solve(N+1,End,Server_PID)  
  end.

divisors(N) ->  
  divisor(N,round(math:sqrt(N))).

divisor(_,0) -> 1;  
divisor(N,I) ->  
  case (N rem I) =:= 0 of  
  true ->  
    2+divisor(N,I-1);  
  false ->  
    divisor(N,I-1)  
  end.

Le test ci-dessous s’est déroulé sur: Un processeur Intel (R) Atom (TM) N270 à 1,60 GHz

~$ time erl -noshell -s p12dist start

The result is: 76576500.

^C

BREAK: (a)bort (c)ontinue (p)roc info (i)nfo (l)oaded
       (v)ersion (k)ill (D)b-tables (d)istribution
a

real    0m5.510s
user    0m5.836s
sys 0m0.152s
7
Mark Washeim

C++ 11, <20ms pour moi - l'exécuter ici

Je comprends que vous vouliez des astuces pour améliorer vos connaissances linguistiques, mais comme cela a été traité en détail ici, j’ai pensé ajouter un contexte aux personnes ayant pu consulter le commentaire de mathematica sur votre question, etc., et me demander pourquoi cela le code était tellement plus lent.

Cette réponse est principalement destinée à fournir un contexte permettant aux personnes d'évaluer le code dans votre question/d'autres réponses plus facilement.

Ce code utilise seulement quelques optimisations (laides), sans rapport avec le langage utilisé, basées sur:

  1. chaque numéro est de la forme n (n + 1)/2
  2. n et n + 1 sont coprimes
  3. le nombre de diviseurs est une fonction multiplicative
#include <iostream>
#include <cmath>
#include <Tuple>
#include <chrono>

using namespace std;

// Calculates the divisors of an integer by determining its prime factorisation.

int get_divisors(long long n)
{
    int divisors_count = 1;

    for(long long i = 2;
        i <= sqrt(n);
        /* empty */)
    {
        int divisions = 0;
        while(n % i == 0)
        {
            n /= i;
            divisions++;
        }

        divisors_count *= (divisions + 1);

        //here, we try to iterate more efficiently by skipping
        //obvious non-primes like 4, 6, etc
        if(i == 2)
            i++;
        else
            i += 2;
    }

    if(n != 1) //n is a prime
        return divisors_count * 2;
    else
        return divisors_count;
}

long long euler12()
{
    //n and n + 1
    long long n, n_p_1;

    n = 1; n_p_1 = 2;

    // divisors_x will store either the divisors of x or x/2
    // (the later iff x is divisible by two)
    long long divisors_n = 1;
    long long divisors_n_p_1 = 2;

    for(;;)
    {
        /* This loop has been unwound, so two iterations are completed at a time
         * n and n + 1 have no prime factors in common and therefore we can
         * calculate their divisors separately
         */

        long long total_divisors;                 //the divisors of the triangle number
                                                  // n(n+1)/2

        //the first (unwound) iteration

        divisors_n_p_1 = get_divisors(n_p_1 / 2); //here n+1 is even and we

        total_divisors =
                  divisors_n
                * divisors_n_p_1;

        if(total_divisors > 1000)
            break;

        //move n and n+1 forward
        n = n_p_1;
        n_p_1 = n + 1;

        //fix the divisors
        divisors_n = divisors_n_p_1;
        divisors_n_p_1 = get_divisors(n_p_1);   //n_p_1 is now odd!

        //now the second (unwound) iteration

        total_divisors =
                  divisors_n
                * divisors_n_p_1;

        if(total_divisors > 1000)
            break;

        //move n and n+1 forward
        n = n_p_1;
        n_p_1 = n + 1;

        //fix the divisors
        divisors_n = divisors_n_p_1;
        divisors_n_p_1 = get_divisors(n_p_1 / 2);   //n_p_1 is now even!
    }

    return (n * n_p_1) / 2;
}

int main()
{
    for(int i = 0; i < 1000; i++)
    {
        using namespace std::chrono;
        auto start = high_resolution_clock::now();
        auto result = euler12();
        auto end = high_resolution_clock::now();

        double time_elapsed = duration_cast<milliseconds>(end - start).count();

        cout << result << " " << time_elapsed << '\n';
    }
    return 0;
}

Cela prend environ 19 ms en moyenne pour mon ordinateur de bureau et 80 ms pour mon ordinateur portable, ce qui est loin de la plupart des autres codes que j'ai vus ici. Et il y a sans doute de nombreuses optimisations encore disponibles.

5
user3125280

Essayer GO:

package main

import "fmt"
import "math"

func main() {
    var n, m, c int
    for i := 1; ; i++ {
        n, m, c = i * (i + 1) / 2, int(math.Sqrt(float64(n))), 0
        for f := 1; f < m; f++ {
            if n % f == 0 { c++ }
    }
    c *= 2
    if m * m == n { c ++ }
    if c > 1001 {
        fmt.Println(n)
        break
        }
    }
}

Je reçois:

version c originale: 9.1690 100%
allez: 8.2520 111%

Mais en utilisant:

package main

import (
    "math"
    "fmt"
 )

// Sieve of Eratosthenes
func PrimesBelow(limit int) []int {
    switch {
        case limit < 2:
            return []int{}
        case limit == 2:
            return []int{2}
    }
    sievebound := (limit - 1) / 2
    sieve := make([]bool, sievebound+1)
    crosslimit := int(math.Sqrt(float64(limit))-1) / 2
    for i := 1; i <= crosslimit; i++ {
        if !sieve[i] {
            for j := 2 * i * (i + 1); j <= sievebound; j += 2*i + 1 {
                sieve[j] = true
            }
        }
    }
    plimit := int(1.3*float64(limit)) / int(math.Log(float64(limit)))
    primes := make([]int, plimit)
    p := 1
    primes[0] = 2
    for i := 1; i <= sievebound; i++ {
        if !sieve[i] {
            primes[p] = 2*i + 1
            p++
            if p >= plimit {
                break
            }
        }
    }
    last := len(primes) - 1
    for i := last; i > 0; i-- {
        if primes[i] != 0 {
            break
        }
        last = i
    }
    return primes[0:last]
}



func main() {
    fmt.Println(p12())
}
// Requires PrimesBelow from utils.go
func p12() int {
    n, dn, cnt := 3, 2, 0
    primearray := PrimesBelow(1000000)
    for cnt <= 1001 {
        n++
        n1 := n
        if n1%2 == 0 {
            n1 /= 2
        }
        dn1 := 1
        for i := 0; i < len(primearray); i++ {
            if primearray[i]*primearray[i] > n1 {
                dn1 *= 2
                break
            }
            exponent := 1
            for n1%primearray[i] == 0 {
                exponent++
                n1 /= primearray[i]
            }
            if exponent > 1 {
                dn1 *= exponent
            }
            if n1 == 1 {
                break
            }
        }
        cnt = dn * dn1
        dn = dn1
    }
    return n * (n - 1) / 2
}

Je reçois:

version c originale: 9.1690 100%
La version c de thaumkid: 0.1060 8650%
version première version: 8.2520 111%
deuxième version: 0.0230 9865%

J'ai aussi essayé Python3.6 et pypy3.3-5.5-alpha:

version c originale: 8.629 100%
La version c de thaumkid: 0.109 7916%
Python3.6: 54.795 16%
pypy3.3-5.5-alpha: 13.291 65%

et puis avec le code suivant j'ai eu:

version c originale: 8.629 100%
La version c de thaumkid: 0.109 8650%
Python3.6: 1.489 580%
pypy3.3-5.5-alpha: 0.582 1483%

def D(N):
    if N == 1: return 1
    sqrtN = int(N ** 0.5)
    nf = 1
    for d in range(2, sqrtN + 1):
        if N % d == 0:
            nf = nf + 1
    return 2 * nf - (1 if sqrtN**2 == N else 0)

L = 1000
Dt, n = 0, 0

while Dt <= L:
    t = n * (n + 1) // 2
    Dt = D(n/2)*D(n+1) if n%2 == 0 else D(n)*D((n+1)/2)
    n = n + 1

print (t)
5
thanos

J'ai supposé que le nombre de facteurs n'est important que si les nombres en question comportent de nombreux facteurs mineurs. J'ai donc utilisé l'excellent algorithme de thaumkid, mais j'ai d'abord utilisé une approximation du nombre de facteurs jamais trop petite. C'est assez simple: Recherchez des facteurs premiers jusqu'à 29, puis vérifiez le nombre restant et calculez une limite supérieure pour le nombre de facteurs. Utilisez cette option 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 un contrôle complet.

Voici le code:

// Return at least the number of factors of n.
static uint64_t approxfactorcount (uint64_t n)
{
    uint64_t count = 1, add;

#define CHECK(d)                            \
    do {                                    \
        if (n % d == 0) {                   \
            add = count;                    \
            do { n /= d; count += add; }    \
            while (n % d == 0);             \
        }                                   \
    } while (0)

    CHECK ( 2); CHECK ( 3); CHECK ( 5); CHECK ( 7); CHECK (11); CHECK (13);
    CHECK (17); CHECK (19); CHECK (23); CHECK (29);
    if (n == 1) return count;
    if (n < 1ull * 31 * 31) return count * 2;
    if (n < 1ull * 31 * 31 * 37) return count * 4;
    if (n < 1ull * 31 * 31 * 37 * 37) return count * 8;
    if (n < 1ull * 31 * 31 * 37 * 37 * 41) return count * 16;
    if (n < 1ull * 31 * 31 * 37 * 37 * 41 * 43) return count * 32;
    if (n < 1ull * 31 * 31 * 37 * 37 * 41 * 43 * 47) return count * 64;
    if (n < 1ull * 31 * 31 * 37 * 37 * 41 * 43 * 47 * 53) return count * 128;
    if (n < 1ull * 31 * 31 * 37 * 37 * 41 * 43 * 47 * 53 * 59) return count * 256;
    if (n < 1ull * 31 * 31 * 37 * 37 * 41 * 43 * 47 * 53 * 59 * 61) return count * 512;
    if (n < 1ull * 31 * 31 * 37 * 37 * 41 * 43 * 47 * 53 * 59 * 61 * 67) return count * 1024;
    if (n < 1ull * 31 * 31 * 37 * 37 * 41 * 43 * 47 * 53 * 59 * 61 * 67 * 71) return count * 2048;
    if (n < 1ull * 31 * 31 * 37 * 37 * 41 * 43 * 47 * 53 * 59 * 61 * 67 * 71 * 73) return count * 4096;
    return count * 1000000;
}

// Return the number of factors of n.
static uint64_t factorcount (uint64_t n)
{
    uint64_t count = 1, add;

    CHECK (2); CHECK (3);

    uint64_t d = 5, inc = 2;
    for (; d*d <= n; d += inc, inc = (6 - inc))
        CHECK (d);

    if (n > 1) count *= 2; // n must be a prime number
    return count;
}

// Prints triangular numbers with record numbers of factors.
static void printrecordnumbers (uint64_t limit)
{
    uint64_t record = 30000;

    uint64_t count1, factor1;
    uint64_t count2 = 1, factor2 = 1;

    for (uint64_t n = 1; n <= limit; ++n)
    {
        factor1 = factor2;
        count1 = count2;

        factor2 = n + 1; if (factor2 % 2 == 0) factor2 /= 2;
        count2 = approxfactorcount (factor2);

        if (count1 * count2 > record)
        {
            uint64_t factors = factorcount (factor1) * factorcount (factor2);
            if (factors > record)
            {
                printf ("%lluth triangular number = %llu has %llu factors\n", n, factor1 * factor2, factors);
                record = factors;
            }
        }
    }
}

Le 14 753 024ème triangle avec 13824 facteurs s'affiche en environ 0,7 seconde, le 879 207 615è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 064e nombre triangulaire avec 172,032 facteurs. 21 minutes 25 secondes (Core2 Duo à 2,4 GHz), ce code ne prend donc que 116 cycles de processeur par nombre en moyenne. Le dernier nombre triangulaire lui-même est supérieur à 2 ^ 68, donc

1
gnasher729

Changement: case (divisor(T,round(math:sqrt(T))) > 500) of

À: case (divisor(T,round(math:sqrt(T))) > 1000) of

Cela produira la réponse correcte pour l'exemple multi-processus d'Erlang.

1
user3051214

J'ai modifié la version "Jannich Brendle" à 1000 au lieu de 500. Et listez le résultat de euler12.bin, euler12.erl, p12dist.erl. Les deux codes erl utilisent '+ native' pour compiler.

zhengs-MacBook-Pro:workspace zhengzhibin$ time erl -noshell -s p12dist start
The result is: 842161320.

real    0m3.879s
user    0m14.553s
sys     0m0.314s
zhengs-MacBook-Pro:workspace zhengzhibin$ time erl -noshell -s euler12 solve
842161320

real    0m10.125s
user    0m10.078s
sys     0m0.046s
zhengs-MacBook-Pro:workspace zhengzhibin$ time ./euler12.bin 
842161320

real    0m5.370s
user    0m5.328s
sys     0m0.004s
zhengs-MacBook-Pro:workspace zhengzhibin$
0
Witeman
#include <stdio.h>
#include <math.h>

int factorCount (long n)
{
    double square = sqrt (n);
    int isquare = (int) square+1;
    long candidate = 2;
    int count = 1;
    while(candidate <= isquare && candidate<=n){
        int c = 1;
        while (n % candidate == 0) {
           c++;
           n /= candidate;
        }
        count *= c;
        candidate++;
    }
    return count;
}

int main ()
{
    long triangle = 1;
    int index = 1;
    while (factorCount (triangle) < 1001)
    {
        index ++;
        triangle += index;
    }
    printf ("%ld\n", triangle);
}

gcc -lm -Ofast euler.c

temps ./a.out

2.79s utilisateur 0.00s système 99% cpu 2,794 total

0
user3250089