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.
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)
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
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
Réponses grossières:
(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 ...)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!
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.
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.
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?
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)
}
}
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.
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
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:
et la course 1M (16386 résultats):
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.
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%!
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.723s
Go: 2.743s
Avec 1000000 (1M au lieu de 100K): C: 3m35.458s
Go: 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.458s
Go: 3m36.259s
Go using goroutines:
3m27.137s2m16.125s
La version 100k:
C: 2.723s
Go: 2.743s
Go using goroutines: 1.706s
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 .
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")
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.