web-dev-qa-db-fra.com

Interpréter un benchmark en C, Clojure, Python, Ruby, Scala et autres

Clause de non-responsabilité

Je sais que les repères artificiels sont mauvais. Ils peuvent afficher des résultats uniquement pour une situation étroite très spécifique. Je ne suppose pas qu'une langue est meilleure que l'autre à cause du banc stupide. Cependant, je me demande pourquoi les résultats sont si différents. Veuillez voir mes questions en bas.

Description du benchmark mathématique

Le benchmark est de simples calculs mathématiques pour trouver des paires de nombres premiers qui diffèrent de 6 (soi-disant nombres premiers sexy ) Par exemple. des nombres premiers sexy inférieurs à 100 seraient: (5 11) (7 13) (11 17) (13 19) (17 23) (23 29) (31 37) (37 43) (41 47) (47 53) (53 59) (61 67) (67 73) (73 79) (83 89) (97 103)

Tableau des résultats

Dans le tableau: temps de calcul en secondes En cours d'exécution: tout sauf Factor s'exécutait dans VirtualBox (invité Debian AMD64 instable, hôte Windows 7 x64) CPU: AMD A4-3305M

  Sexy primes up to:        10k      20k      30k      100k               

  Bash                    58.00   200.00     [*1]      [*1]

  C                        0.20     0.65     1.42     15.00

  Clojure1.4               4.12     8.32    16.00    137.93

  Clojure1.4 (optimized)   0.95     1.82     2.30     16.00

  Factor                    n/a      n/a    15.00    180.00

  Python2.7                1.49     5.20    11.00       119     

  Ruby1.8                  5.10    18.32    40.48    377.00

  Ruby1.9.3                1.36     5.73    10.48    106.00

  Scala2.9.2               0.93     1.41     2.73     20.84

  Scala2.9.2 (optimized)   0.32     0.79     1.46     12.01

[* 1] - J'ai peur d'imaginer combien de temps cela prendra

Listes de codes

C:

int isprime(int x) {
  int i;
  for (i = 2; i < x; ++i)
    if (x%i == 0) return 0;
  return 1;
}

void findprimes(int m) {
  int i;
  for ( i = 11; i < m; ++i)
    if (isprime(i) && isprime(i-6))
      printf("%d %d\n", i-6, i);
}

main() {
    findprimes(10*1000);
}

Rubis:

def is_prime?(n)
  (2...n).all?{|m| n%m != 0 }
end

def sexy_primes(x)
  (9..x).map do |i|
    [i-6, i]
  end.select do |j|
    j.all?{|j| is_prime? j}
  end
end

a = Time.now
p sexy_primes(10*1000)
b = Time.now
puts "#{(b-a)*1000} mils"

Scala:

def isPrime(n: Int) =
  (2 until n) forall { n % _ != 0 }

def sexyPrimes(n: Int) = 
  (11 to n) map { i => List(i-6, i) } filter { _ forall(isPrime(_)) }

val a = System.currentTimeMillis()
println(sexyPrimes(100*1000))
val b = System.currentTimeMillis()
println((b-a).toString + " mils")

Scala a optimisé isPrime (la même idée que dans l'optimisation de Clojure):

import scala.annotation.tailrec

@tailrec // Not required, but will warn if optimization doesn't work
def isPrime(n: Int, i: Int = 2): Boolean = 
  if (i == n) true 
  else if (n % i != 0) isPrime(n, i + 1)
  else false

Clojure:

(defn is-prime? [n]
  (every? #(> (mod n %) 0)
    (range 2 n)))

(defn sexy-primes [m]
  (for [x (range 11 (inc m))
        :let [z (list (- x 6) x)]
        :when (every? #(is-prime? %) z)]
      z))

(let [a (System/currentTimeMillis)]
  (println (sexy-primes (* 10 1000)))
  (let [b (System/currentTimeMillis)]
    (println (- b a) "mils")))

Clojure optimisé is-prime?:

(defn ^:static is-prime? [^long n]
  (loop [i (long 2)] 
    (if (= (rem n i) 0)
      false
      (if (>= (inc i) n) true (recur (inc i))))))

Python

import time as time_

def is_prime(n):
  return all((n%j > 0) for j in xrange(2, n))

def primes_below(x):
  return [[j-6, j] for j in xrange(9, x+1) if is_prime(j) and is_prime(j-6)]

a = int(round(time_.time() * 1000))
print(primes_below(10*1000))
b = int(round(time_.time() * 1000))
print(str((b-a)) + " mils")

Facteur

MEMO:: prime? ( n -- ? )
n 1 - 2 [a,b] [ n swap mod 0 > ] all? ;

MEMO: sexyprimes ( n n -- r r )
[a,b] [ prime? ] filter [ 6 + ] map [ prime? ] filter dup [ 6 - ] map ;

5 10 1000 * sexyprimes . .

Bash (zsh):

#!/usr/bin/zsh
function prime {
  for (( i = 2; i < $1; i++ )); do
    if [[ $[$1%i] == 0 ]]; then
      echo 1
      exit
    fi
  done
  echo 0
}

function sexy-primes {
  for (( i = 9; i <= $1; i++ )); do
    j=$[i-6]
    if [[ $(prime $i) == 0 && $(prime $j) == 0 ]]; then
      echo $j $i
    fi
  done
}

sexy-primes 10000

Des questions

  1. Pourquoi Scala est si rapide? Est-ce à cause de frappe statique ? Ou utilise-t-il simplement JVM très efficacement?
  2. Pourquoi une si grande différence entre Ruby et Python? Je pensais que ces deux ne sont pas quelque peu totalement différents. Peut-être que mon code est faux. Veuillez m'éclairer! Merci.  [~ # ~] upd [~ # ~] Oui, c'était une erreur dans mon code. Python et Ruby 1.9 sont assez égaux.
  3. Saut de productivité vraiment impressionnant entre les versions Ruby.
  4. Puis-je optimiser le code Clojure en ajoutant des déclarations de type? Cela va-t-il aider?
91
defhlt

Réponses grossières:

  1. Le typage statique de Scala l'aide un peu ici - cela signifie qu'il utilise la JVM assez efficacement sans trop d'effort supplémentaire.
  2. Je ne suis pas exactement sûr de la différence Ruby/Python, mais je soupçonne que (2...n).all? dans la fonction is-prime? est susceptible d'être assez bien optimisé en Ruby (EDIT: sonne comme c'est effectivement le cas, voir la réponse de Julian pour plus de détails ...)
  3. Ruby 1.9.3 est juste beaucoup mieux optimisé
  4. Le code Clojure peut certainement être beaucoup accéléré! Bien que Clojure soit dynamique par défaut, vous pouvez utiliser des indices de type, des mathématiques primitives, etc. pour vous rapprocher de Scala/pure Java speed dans de nombreux cas, lorsque vous en avez besoin) à.

L'optimisation la plus importante dans le code Clojure serait d'utiliser des mathématiques primitives typées dans is-prime?, quelque chose comme:

(set! *unchecked-math* true) ;; at top of file to avoid using BigIntegers

(defn ^:static is-prime? [^long n]
  (loop [i (long 2)] 
    (if (zero? (mod n i))
      false
      (if (>= (inc i) n) true (recur (inc i))))))

Avec cette amélioration, j'obtiens Clojure complétant 10k en 0,635 secondes (c'est-à-dire le deuxième plus rapide de votre liste, battant Scala)

PS notez que vous avez du code d'impression dans votre benchmark dans certains cas - ce n'est pas une bonne idée car cela faussera les résultats, surtout si l'utilisation d'une fonction comme print pour la première fois provoque l'initialisation des sous-systèmes IO ou quelque chose comme ça!

30
mikera

Voici une version rapide de Clojure, utilisant les mêmes algorithmes de base:

(set! *unchecked-math* true)

(defn is-prime? [^long n]
  (loop [i 2]
    (if (zero? (unchecked-remainder-int n i))
      false
      (if (>= (inc i) n)
        true
        (recur (inc i))))))

(defn sexy-primes [m]
  (for [x (range 11 (inc m))
        :when (and (is-prime? x) (is-prime? (- x 6)))]
    [(- x 6) x]))

Il fonctionne environ 20 fois plus vite que votre original sur ma machine. Et voici une version qui exploite la nouvelle bibliothèque de réducteurs en 1.5 (nécessite Java 7 ou JSR 166):

(require '[clojure.core.reducers :as r]) ;'

(defn sexy-primes [m]
  (->> (vec (range 11 (inc m)))
       (r/filter #(and (is-prime? %) (is-prime? (- % 6))))
       (r/map #(list (- % 6) %))
       (r/fold (fn ([] []) ([a b] (into a b))) conj)))

Cela fonctionne environ 40 fois plus vite que votre original. Sur ma machine, c'est 100k en 1,5 seconde.

23
Justin Kramer

Je répondrai juste # 2, car c'est le seul que j'ai quelque chose d'intelligent à distance à dire, mais pour votre Python, vous créez une liste intermédiaire dans is_prime, tandis que vous utilisez .map dans votre all dans Ruby qui est juste une itération.

Si vous changez votre is_prime à:

def is_prime(n):
    return all((n%j > 0) for j in range(2, n))

ils sont à égalité.

Je pourrais optimiser le Python plus loin, mais mon Ruby n'est pas assez bon pour savoir quand j'ai donné plus d'avantages (par exemple, en utilisant xrange fait gagner Python gagne sur ma machine, mais je ne me souviens pas si la Ruby que vous avez utilisée crée une plage entière en mémoire ou non) .

EDIT: Sans être trop stupide, faire ressembler le code Python:

import time

def is_prime(n):
    return all(n % j for j in xrange(2, n))

def primes_below(x):
    return [(j-6, j) for j in xrange(9, x + 1) if is_prime(j) and is_prime(j-6)]

a = int(round(time.time() * 1000))
print(primes_below(10*1000))
b = int(round(time.time() * 1000))
print(str((b-a)) + " mils")

ce qui ne change pas grand-chose, le met à 1,5 s pour moi, et, étant très idiot, le faire tourner avec PyPy le met à 0,3 s pour 10K et 21s pour 100K.

22
Julian

Vous pouvez rendre la Scala beaucoup plus rapide en modifiant votre méthode isPrime en

  def isPrime(n: Int, i: Int = 2): Boolean = 
    if (i == n) true 
    else if (n % i != 0) isPrime(n, i + 1)
    else false

Pas tout à fait aussi concis mais le programme s'exécute dans 40% du temps!

Nous supprimons les objets superflus Range et anonymes Function, le compilateur Scala reconnaît la récursivité de queue et la transforme en boucle while, que la JVM peut se transformer en code machine plus ou moins optimal, il ne devrait donc pas être trop éloigné de la version C.

Voir aussi: Comment optimiser les for-comprehensions et les boucles dans Scala?

16

Voici ma scala en parallèle et sans parallèle, juste pour le plaisir: (Dans mon calcul dual core, la version parallèle prend 335 ms tandis que la version sans parallèle prend 655 ms)

object SexyPrimes {
  def isPrime(n: Int): Boolean = 
    (2 to math.sqrt(n).toInt).forall{ n%_ != 0 }

  def isSexyPrime(n: Int): Boolean = isPrime(n) && isPrime(n-6)

  def findPrimesPar(n: Int) {
    for(k <- (11 to n).par)
      if(isSexyPrime(k)) printf("%d %d\n",k-6,k)
  }

  def findPrimes(n: Int) {
    for(k <- 11 to n)
      if(isSexyPrime(k)) printf("%d %d\n",k-6,k)
  }


  def timeOf(call : =>Unit) {
    val start = System.currentTimeMillis
    call
    val end = System.currentTimeMillis
    println((end-start)+" mils")
  }

  def main(args: Array[String]) {
    timeOf(findPrimes(100*1000))
    println("------------------------")
    timeOf(findPrimesPar(100*1000))
  }
}

EDIT: Selon la suggestion de Emil H, j'ai changé mon code pour éviter les effets de IO et jvm warmup:

Le résultat montre dans mon calcul:

Liste (3432, 1934, 3261, 1716, 3229, 1654, 3214, 1700)

object SexyPrimes {
  def isPrime(n: Int): Boolean = 
    (2 to math.sqrt(n).toInt).forall{ n%_ != 0 }

  def isSexyPrime(n: Int): Boolean = isPrime(n) && isPrime(n-6)

  def findPrimesPar(n: Int) {
    for(k <- (11 to n).par)
      if(isSexyPrime(k)) ()//printf("%d %d\n",k-6,k)
  }

  def findPrimes(n: Int) {
    for(k <- 11 to n)
      if(isSexyPrime(k)) ()//printf("%d %d\n",k-6,k)
  }


  def timeOf(call : =>Unit): Long = {
    val start = System.currentTimeMillis
    call
    val end = System.currentTimeMillis
    end - start 
  }

  def main(args: Array[String]) {
    val xs = timeOf(findPrimes(1000*1000))::timeOf(findPrimesPar(1000*1000))::
             timeOf(findPrimes(1000*1000))::timeOf(findPrimesPar(1000*1000))::
             timeOf(findPrimes(1000*1000))::timeOf(findPrimesPar(1000*1000))::
             timeOf(findPrimes(1000*1000))::timeOf(findPrimesPar(1000*1000))::Nil
    println(xs)
  }
}
8
Eastsun

Peu importe les repères; le problème m'a intéressé et j'ai fait quelques ajustements rapides. Celui-ci utilise le décorateur lru_cache, Qui mémorise une fonction; donc quand nous appelons is_prime(i-6) nous obtenons essentiellement ce premier chèque gratuitement. Ce changement réduit le travail d'environ la moitié. De plus, nous pouvons faire en sorte que les appels range() ne parcourent que les nombres impairs, coupant à nouveau le travail environ en deux.

http://en.wikipedia.org/wiki/Memoization

http://docs.python.org/dev/library/functools.html

Cela nécessite Python 3.2 ou plus récent pour obtenir lru_cache, Mais pourrait fonctionner avec un ancien Python si vous installez un Python recette qui fournit lru_cache. Si vous utilisez Python 2.x, vous devriez vraiment utiliser xrange() au lieu de range().

http://code.activestate.com/recipes/577479-simple-caching-decorator/

from functools import lru_cache
import time as time_

@lru_cache()
def is_prime(n):
    return n%2 and all(n%i for i in range(3, n, 2))

def primes_below(x):
    return [(i-6, i) for i in range(9, x+1, 2) if is_prime(i) and is_prime(i-6)]

correct100 = [(5, 11), (7, 13), (11, 17), (13, 19), (17, 23), (23, 29),
        (31, 37), (37, 43), (41, 47), (47, 53), (53, 59), (61, 67), (67, 73),
        (73, 79), (83, 89)]
assert(primes_below(100) == correct100)

a = time_.time()
print(primes_below(30*1000))
b = time_.time()

elapsed = b - a
print("{} msec".format(round(elapsed * 1000)))

Ce qui précède n'a pris que très peu de temps pour être édité. J'ai décidé d'aller plus loin et de faire en sorte que le test des nombres premiers n'essaye que les diviseurs premiers, et uniquement jusqu'à la racine carrée du nombre testé. La façon dont je l'ai fait ne fonctionne que si vous vérifiez les nombres dans l'ordre, afin qu'il puisse accumuler tous les nombres premiers au fur et à mesure; mais ce problème vérifiait déjà les numéros dans l'ordre, donc ça allait.

Sur mon ordinateur portable (rien de spécial; le processeur est un AMD Turion II à 1,5 GHz "K625"), cette version a produit une réponse de 100K en moins de 8 secondes.

from functools import lru_cache
import math
import time as time_

known_primes = set([2, 3, 5, 7])

@lru_cache(maxsize=128)
def is_prime(n):
    last = math.ceil(math.sqrt(n))
    flag = n%2 and all(n%x for x in known_primes if x <= last)
    if flag:
        known_primes.add(n)
    return flag

def primes_below(x):
    return [(i-6, i) for i in range(9, x+1, 2) if is_prime(i) and is_prime(i-6)]

correct100 = [(5, 11), (7, 13), (11, 17), (13, 19), (17, 23), (23, 29),
        (31, 37), (37, 43), (41, 47), (47, 53), (53, 59), (61, 67), (67, 73),
        (73, 79), (83, 89)]
assert(primes_below(100) == correct100)

a = time_.time()
print(primes_below(100*1000))
b = time_.time()

elapsed = b - a
print("{} msec".format(round(elapsed * 1000)))

Le code ci-dessus est assez facile à écrire en Python, Ruby, etc. mais serait plus pénible en C.

Vous ne pouvez pas comparer les chiffres de cette version avec les chiffres des autres versions sans réécrire les autres pour utiliser des astuces similaires. Je n'essaye pas de prouver quoi que ce soit ici; Je pensais juste que le problème était amusant et je voulais voir quel genre d'améliorations de performances faciles je pouvais glaner.

7
steveha

N'oubliez pas Fortran! (Surtout en plaisantant, mais je m'attendrais à des performances similaires à C). Les instructions avec des points d'exclamation sont facultatives, mais de bon style. (! est un caractère de commentaire dans fortran 90)

logical function isprime(n)
IMPLICIT NONE !
integer :: n,i
do i=2,n
   if(mod(n,i).eq.0)) return .false.
enddo
return .true.
end

subroutine findprimes(m)
IMPLICIT NONE !
integer :: m,i
logical, external :: isprime

do i=11,m
   if(isprime(i) .and. isprime(i-6))then
      write(*,*) i-6,i
   endif
enddo
end

program main
findprimes(10*1000)
end
7
mgilson

Je n'ai pas pu résister à faire quelques-unes des optimisations les plus évidentes pour la version C qui a fait que le test de 100k prend maintenant 0,3 s sur ma machine (5 fois plus rapide que la version C dans la question, toutes deux compilées avec MSVC 2010/Ox) .

int isprime( int x )
{
    int i, n;
    for( i = 3, n = x >> 1; i <= n; i += 2 )
        if( x % i == 0 )
            return 0;
    return 1;
}

void findprimes( int m )
{
    int i, s = 3; // s is bitmask of primes in last 3 odd numbers
    for( i = 11; i < m; i += 2, s >>= 1 ) {
        if( isprime( i ) ) {
            if( s & 1 )
                printf( "%d %d\n", i - 6, i );
            s |= 1 << 3;
        }
    }
}

main() {
    findprimes( 10 * 1000 );
}

Voici l'implémentation identique en Java:

public class prime
{
    private static boolean isprime( final int x )
    {
        for( int i = 3, n = x >> 1; i <= n; i += 2 )
            if( x % i == 0 )
                return false;
        return true;
    }

    private static void findprimes( final int m )
    {
        int s = 3; // s is bitmask of primes in last 3 odd numbers
        for( int i = 11; i < m; i += 2, s >>= 1 ) {
            if( isprime( i ) ) {
                if( ( s & 1 ) != 0 )
                    print( i );
                s |= 1 << 3;
            }
        }
    }

    private static void print( int i )
    {
        System.out.println( ( i - 6 ) + " " + i );
    }

    public static void main( String[] args )
    {
        // findprimes( 300 * 1000 ); // for some JIT training
        long time = System.nanoTime();
        findprimes( 10 * 1000 );
        time = System.nanoTime() - time;
        System.err.println( "time: " + ( time / 10000 ) / 100.0 + "ms" );
    }
}

Avec Java 1.7.0_04 cela fonctionne presque exactement aussi vite que la version C. Client ou serveur VM ne montre pas beaucoup de différence, sauf que la formation JIT semble pour aider le serveur VM un peu (~ 3%) alors qu'il n'a presque aucun effet avec la machine virtuelle cliente. La sortie dans Java semble être plus lente que en C. Si la sortie est remplacée par un compteur statique dans les deux versions, la version Java s'exécute un peu plus vite que la version C.

Voici mes temps pour la course de 100 km:

  • 319ms C compilé avec/Ox et sortie vers> NIL:
  • 312ms C compilé avec/Ox et compteur statique
  • 324ms Java client VM avec sortie vers> NIL:
  • 299ms Java client VM avec compteur statique

et la course 1M (16386 résultats):

  • 24.95s C compilé avec/Ox et compteur statique
  • 25.08s Java client VM avec compteur statique
  • 24.86s Java serveur VM avec compteur statique

Bien que cela ne réponde pas vraiment à vos questions, cela montre que de petits ajustements peuvent avoir un impact notable sur les performances. Donc, pour pouvoir vraiment comparer les langues, vous devriez essayer d'éviter autant que possible toutes les différences algorithmiques.

Il donne également un indice pourquoi Scala semble plutôt rapide. Il fonctionne sur le Java VM et bénéficie ainsi de son impressionnant performance.

6
x4u

Juste pour le plaisir, voici une version parallèle Ruby.

require 'benchmark'

num = ARGV[0].to_i

def is_prime?(n)
  (2...n).all?{|m| n%m != 0 }
end

def sexy_primes_default(x)
    (9..x).map do |i|
        [i-6, i]
    end.select do |j|
        j.all?{|j| is_prime? j}
    end
end

def sexy_primes_threads(x)
    partition = (9..x).map do |i|
        [i-6, i]
    end.group_by do |x|
        x[0].to_s[-1]
    end
    threads = Array.new
    partition.each_key do |k|
       threads << Thread.new do
            partition[k].select do |j|
                j.all?{|j| is_prime? j}
            end
        end
    end
    threads.each {|t| t.join}
    threads.map{|t| t.value}.reject{|x| x.empty?}
end

puts "Running up to num #{num}"

Benchmark.bm(10) do |x|
    x.report("default") {a = sexy_primes_default(num)}
    x.report("threads") {a = sexy_primes_threads(num)}
end

Sur mon MacBook Air Core i5 à 1,8 GHz, les performances sont les suivantes:

# Ruby 1.9.3
$ ./sexyprimes.rb 100000
Running up to num 100000
                 user     system      total        real
default     68.840000   0.060000  68.900000 ( 68.922703)
threads     71.730000   0.090000  71.820000 ( 71.847346)

# JRuby 1.6.7.2 on JVM 1.7.0_05
$ jruby --1.9 --server sexyprimes.rb 100000
Running up to num 100000
                user     system      total        real
default    56.709000   0.000000  56.709000 ( 56.708000)
threads    36.396000   0.000000  36.396000 ( 36.396000)

# JRuby 1.7.0.preview1 on JVM 1.7.0_05
$ jruby --server sexyprimes.rb 100000
Running up to num 100000
             user     system      total        real
default     52.640000   0.270000  52.910000 ( 51.393000)
threads    105.700000   0.290000 105.990000 ( 30.298000)

Il semble que le JIT de la JVM donne Ruby une belle amélioration des performances dans le cas par défaut, tandis que le véritable multithreading aide JRuby à effectuer 50% plus rapidement dans le cas fileté. Ce qui est plus intéressant, c'est que JRuby 1.7 améliore la JRuby 1.6 marque un bon 17%!

4
Georgios Gousios

Voici le code de la version Go (golang.org):

package main

import (
    "fmt"
)


func main(){
    findprimes(10*1000)
}

func isprime(x int) bool {
    for i := 2; i < x; i++ {
        if x%i == 0 {
            return false
        }
    }
    return true
}

func findprimes(m int){
    for i := 11; i < m; i++ {
        if isprime(i) && isprime(i-6) {
            fmt.Printf("%d %d\n", i-6, i)
        }
    }
}

Il a fonctionné aussi vite que la version C.

en utilisant un Asus u81a Intel Core 2 Duo T6500 2,1 GHz, 2 Mo de cache L2, 800 MHz FSB. 4 Go de RAM

La version 100k: C: 2.723sGo: 2.743s

Avec 1000000 (1M au lieu de 100K): C: 3m35.458sGo: 3m36.259s

Mais je pense qu'il serait juste d'utiliser les capacités de multithreading intégrées de Go et de comparer cette version avec la version C normale (sans multithreading), simplement parce qu'il est presque trop facile de faire du multithreading avec Go.

Mise à jour: j'ai fait une version parallèle en utilisant Goroutines in Go:

package main

import (
  "fmt"
  "runtime"
)

func main(){
    runtime.GOMAXPROCS(4)
    printer := make(chan string)
    printer2 := make(chan string)
    printer3 := make(chan string)
    printer4 := make(chan string)
    finished := make(chan int)

    var buffer, buffer2, buffer3 string

    running := 4
    go findprimes(11, 30000, printer, finished)
    go findprimes(30001, 60000, printer2, finished)
    go findprimes(60001, 85000, printer3, finished)
    go findprimes(85001, 100000, printer4, finished)

    for {
      select {
        case i := <-printer:
          // batch of sexy primes received from printer channel 1, print them
          fmt.Printf(i)
        case i := <-printer2:
          // sexy prime list received from channel, store it
          buffer = i
        case i := <-printer3:
          // sexy prime list received from channel, store it
          buffer2 = i
        case i := <-printer4:
          // sexy prime list received from channel, store it
          buffer3 = i
        case <-finished:
          running--
          if running == 0 {
              // all goroutines ended
              // dump buffer to stdout
              fmt.Printf(buffer)
              fmt.Printf(buffer2)
              fmt.Printf(buffer3)
              return
          }
      }
    }
}

func isprime(x int) bool {
    for i := 2; i < x; i++ {
        if x%i == 0 {
            return false
        }
    }
    return true
}

func findprimes(from int, to int, printer chan string, finished chan int){
    str := ""
    for i := from; i <= to; i++ {
        if isprime(i) && isprime(i-6) {
            str = str + fmt.Sprintf("%d %d\n", i-6, i)
      }
    }
    printer <- str
    //fmt.Printf("Finished %d to %d\n", from, to)
    finished <- 1
}

La version parallélisée utilisée en moyenne 2,743 secondes, exactement le même temps que la version régulière utilisée.

La version parallélisée s'est terminée en 1,706 secondes. Il utilisait moins de 1,5 Mo de RAM.

Une chose étrange: mon kubuntu 64 bits dual core n'a jamais culminé dans les deux cœurs. Il semblait que Go n'utilisait qu'un seul cœur. Corrigé avec un appel à runtime.GOMAXPROCS(4)

Mise à jour: j'ai exécuté la version parallèle jusqu'à 1M de numéros. L'un de mes cœurs de processeur était à 100% tout le temps, tandis que l'autre n'était pas utilisé du tout (impair). Cela a pris une minute entière de plus que les versions C et Go standard. :(

Avec 1000000 (1M au lieu de 100K):

C: 3m35.458sGo: 3m36.259sGo using goroutines:3m27.137s2m16.125s

La version 100k:

C: 2.723sGo: 2.743sGo using goroutines: 1.706s

4
Sebastián Grignoli

Dans Scala essayez d'utiliser Tuple2 au lieu de List, cela devrait aller plus vite. Supprimez simplement le mot 'List' car (x, y) est un Tuple2.

Tuple2 est spécialisé pour Int, Long et Double, ce qui signifie qu'il n'aura pas à encadrer/décompresser ces types de données bruts. source Tuple2 . La liste n'est pas spécialisée. Liste des sources .

4
Tomas Lazaro

Basé sur réponse de x4 , j'ai écrit une version scala en utilisant la récursivité, et je l'ai améliorée en allant uniquement vers sqrt au lieu de x/2 pour la fonction de vérification principale .Je reçois ~ 250 ms pour 100k, et ~ 600 ms pour 1 M. Je suis allé de l'avant et suis allé à 10 M en ~ 6 s.

import scala.annotation.tailrec

var count = 0;
def print(i:Int) = {
  println((i - 6) + " " + i)
  count += 1
}

@tailrec def isPrime(n:Int, i:Int = 3):Boolean = {
  if(n % i == 0) return false;
  else if(i * i > n) return true;
  else isPrime(n = n, i = i + 2)
}      

@tailrec def findPrimes(max:Int, bitMask:Int = 3, i:Int = 11):Unit = {
  if (isPrime(i)) {
    if((bitMask & 1) != 0) print(i)
    if(i + 2 < max) findPrimes(max = max, bitMask = (bitMask | (1 << 3)) >> 1, i = i + 2)
  } else if(i + 2 < max) {
    findPrimes(max = max, bitMask = bitMask >> 1, i = i + 2)
  }
}

val a = System.currentTimeMillis()
findPrimes(max=10000000)
println(count)
val b = System.currentTimeMillis()
println((b - a).toString + " mils")

Je suis également retourné et j'ai écrit une version CoffeeScript (V8 JavaScript), qui obtient ~ 15 ms pour 100k, 250ms pour 1M et 6s pour 10M, en utilisant un compteur (en ignorant les E/S). Si j'allume la sortie, cela prend ~ 150 ms pour 100 k, 1 s pour 1 M et 12 s pour 10 M. Impossible d'utiliser la récursion de queue ici, malheureusement, j'ai donc dû la reconvertir en boucles.

count = 0;
print = (i) ->
  console.log("#{i - 6} #{i}")
  count += 1
  return

isPrime = (n) ->
  i = 3
  while i * i < n
    if n % i == 0
      return false
    i += 2
  return true

findPrimes = (max) ->
  bitMask = 3
  for i in [11..max] by 2
    prime = isPrime(i)
    if prime
      if (bitMask & 1) != 0
        print(i)
      bitMask |= (1 << 3)
    bitMask >>= 1
  return

a = new Date()
findPrimes(1000000)
console.log(count)
b = new Date()
console.log((b - a) + " ms")
3
Eve Freeman

La réponse à votre question n ° 1 est que Oui, la JVM est incroyablement rapide et oui la saisie statique aide.

La JVM devrait être plus rapide que C à long terme, peut-être même plus vite que le langage d'assemblage "normal" - Bien sûr, vous pouvez toujours optimiser l'assemblage pour battre quoi que ce soit en faisant un profil d'exécution manuel et en créant une version distincte pour chaque CPU, vous venez de doivent être incroyablement bons et bien informés.

Les raisons de la vitesse de Java sont:

La JVM peut analyser votre code pendant son exécution et l'optimiser manuellement - par exemple, si vous aviez une méthode qui pouvait être analysée statiquement au moment de la compilation pour être une vraie fonction et la JVM a remarqué que vous l'appeliez souvent avec la même paramètres, il POURRAIT en fait éliminer complètement l'appel et simplement injecter les résultats du dernier appel (je ne sais pas si Java fait exactement cela, mais il fait beaucoup de choses comme ça) .

En raison du typage statique, la JVM peut en savoir beaucoup sur votre code au moment de la compilation, ce qui lui permet de pré-optimiser pas mal de choses. Il permet également au compilateur d'optimiser chaque classe individuellement sans savoir comment une autre classe prévoit de l'utiliser. Aussi Java n'a pas de pointeurs arbitraires vers l'emplacement mémoire, il SAIT quelles valeurs en mémoire peuvent et ne peuvent pas être modifiées et peut optimiser en conséquence.

L'allocation de tas est BEAUCOUP plus efficace que C, l'allocation de tas de Java ressemble plus à l'allocation de pile de C en vitesse - mais plus polyvalente. Beaucoup de temps est passé dans les différents algroithims utilisés ici, c'est un art - par exemple, tous les objets avec une courte durée de vie (comme les variables de pile de C) sont alloués à un emplacement libre "connu" (pas de recherche d'un endroit libre avec suffisamment d'espace) et sont tous libérés ensemble en une seule étape (comme une pile pop).

La machine virtuelle Java peut connaître les particularités de l'architecture de votre processeur et générer du code machine spécifiquement pour un processeur donné.

La JVM peut accélérer votre code longtemps après l'avoir expédié. Tout comme déplacer un programme vers un nouveau processeur peut l'accélérer, le déplacer vers une nouvelle version de la JVM peut également vous donner des performances de vitesse énormes adaptées aux processeurs qui n'existaient même pas lorsque vous avez initialement compilé votre code, quelque chose c ne peut physiquement pas se passer d'un recomiple.

Soit dit en passant, la plupart des mauvais représentants pour Java vient du long temps de démarrage pour charger la JVM (un jour, quelqu'un construira la JVM dans le système d'exploitation et cela disparaîtra!)) Et le fait que de nombreux développeurs sont vraiment mauvais à écrire du code GUI (en particulier threadé), ce qui a fait que les interfaces graphiques Java) deviennent souvent insensibles et glitchy. Des langages simples comme Java et VB ont leurs défauts amplifiés par le fait que les capacités du programmeur moyen ont tendance à être plus faibles que les langages plus compliqués.

2
Bill K