Interpretation einer Benchmark in C, Clojure, Python, Ruby, Scala und andere

Haftungsausschluss

Ich weiß, dass künstliche Benchmarks böse sind. Sie können nur Ergebnisse für eine sehr spezifische schmale Situation zeigen. Ich gehe nicht davon aus, dass eine Sprache besser ist als die andere wegen der etwas dummen Bank. Allerdings frage ich mich, warum die Ergebnisse so unterschiedlich sind. Bitte sehen Sie meine Fragen unten.

Math Benchmark Beschreibung

Benchmark ist eine einfache Mathematikberechnung, um Paare von Primzahlen zu finden, die sich um 6 unterscheiden (so genannte sexy Primzahlen ) zB sexy Primzahlen unter 100 wäre: (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)

Ergebnistabelle

In Tabelle: Berechnungszeit in Sekunden Laufen: Alles außer Faktor wurde in VirtualBox ausgeführt (Debian unstable amd64 Gast, Windows 7 x64 Host) 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/an/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] – Ich habe Angst, mir vorzustellen, wie viel Zeit es dauert

Codeauflistungen

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); } 

Rubin:

 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 "#{(ba)*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((ba).toString + " mils") 

Scala opimized isPrime (die gleiche Idee wie in Clojure-Optimierung):

 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 (- ba) "mils"))) 

Clojure optimiert is-prime? :

 (defn ^:static is-prime? [^long n] (loop [i (long 2)] (if (= (rem ni) 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((ba)) + " mils") 

Faktor

 MEMO:: prime? ( n -- ? ) n 1 - 2 [a,b] [ n swap mod 0 > ] all? ; MEMO: sexyprimes ( nn -- rr ) [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 

Fragen

  1. Warum ist Scala so schnell? Ist es wegen der statischen Typisierung ? Oder es ist einfach nur mit JVM sehr effizient?
  2. Warum so ein großer Unterschied zwischen Ruby und Python? Ich dachte, diese beiden sind nicht ganz anders. Vielleicht ist mein Code falsch Bitte erleuchte mich! Vielen Dank. UPD Ja, das war Fehler in meinem Code. Python und Ruby 1.9 sind ziemlich gleich.
  3. Wirklich eindrucksvoller Sprung in der Produktivität zwischen Ruby-Versionen.
  4. Kann ich den Clojure-Code durch Hinzufügen von Typdeklarationen optimieren? Wird es helfen?

13 Solutions collect form web for “Interpretation einer Benchmark in C, Clojure, Python, Ruby, Scala und andere”

Grobe Antworten:

  1. Die statische Typisierung von Scala hilft ihm ein bisschen hier – das bedeutet, dass es die JVM ziemlich effizient ohne zu viel Aufwand nutzt.
  2. Ich bin nicht ganz sicher auf den Ruby / Python Unterschied, aber ich vermute, dass (2...n).all? In der Funktion is-prime? Dürfte in Ruby sehr gut optimiert sein (EDIT: klingt wie das ist ja der Fall, siehe Julians Antwort für mehr Details …)
  3. Ruby 1.9.3 ist einfach viel besser optimiert
  4. Clojure-Code kann sicherlich viel beschleunigt werden! Während Clojure ist standardmäßig dynamisch, können Sie Typ Hinweise, primitive Mathematik etc., um in der Nähe von Scala / reine Java-Geschwindigkeit in vielen Fällen, wenn Sie müssen.

Die wichtigste Optimierung im Clojure-Code wäre die Verwendung von typisierten primitiven Mathematikern in is-prime? , etwas wie:

 (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 ni)) false (if (>= (inc i) n) true (recur (inc i)))))) 

Mit dieser Verbesserung bekomme ich Clojure 10k in 0.635 Sek. (Dh die zweitschnellste auf deiner Liste, die Scala schlägt)

PS beachten Sie, dass Sie in einigen Fällen Druckcode in Ihrem Benchmark haben – nicht eine gute Idee, da es die Ergebnisse verzerren wird, vor allem, wenn eine Funktion wie print zum ersten Mal die Initialisierung von IO-Subsystemen oder so ähnlich macht!

Ich werde nur # 2 beantworten, da es die einzige ist, die ich irgendetwas fern intelligentes zu sagen habe, aber für deinen Python-Code is_prime du eine Zwischenliste in is_prime , während du mit .map in deinem all in Ruby, der nur iteriert.

Wenn du deine is_prime zu:

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

Sie sind auf par.

Ich könnte den Python weiter optimieren, aber mein Ruby ist nicht gut genug, um zu wissen, wann ich mehr von einem Vorteil gegeben habe (zB mit xrange macht Python auf meiner Maschine, aber ich erinnere mich nicht, ob die Ruby-Reihe, die du benutzt hast Schafft eine ganze Palette im Speicher oder nicht).

EDIT: Ohne zu albern zu sein, wie der Python-Code aussieht:

 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((ba)) + " mils") 

Die sich nicht viel mehr ändert, legt es bei 1.5s für mich, und mit extra dumm, läuft es mit PyPy setzt es auf .3s für 10K und 21s für 100K.

Hier ist eine schnelle Clojure-Version mit den gleichen Grundalgorithmen:

 (set! *unchecked-math* true) (defn is-prime? [^long n] (loop [i 2] (if (zero? (unchecked-remainder-int ni)) 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])) 

Es läuft etwa 20x schneller als dein Original auf meiner Maschine. Und hier ist eine Version, die die neue Reduzier-Bibliothek in 1.5 nutzt (erfordert Java 7 oder 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 ([] []) ([ab] (into ab))) conj))) 

Das läuft etwa 40x schneller als dein Original. Auf meiner Maschine, das ist 100k in 1,5 Sekunden.

Sie können die Scala viel schneller machen, indem Sie Ihre isPrime Methode isPrime

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

Nicht ganz so prägnant, aber das Programm läuft in 40% der Zeit!

Wir schneiden die überflüssigen Range und anonymen Function aus, der Scala-Compiler erkennt die Schwanzrekursion und verwandelt ihn in eine while-Schleife, die die JVM in mehr oder weniger optimalen Maschinencode verwandeln kann, also sollte es nicht zu weit weg sein Die C-Version.

Siehe auch: Wie optimiere ich Verständnisse und Loops in Scala?

Hier ist meine Scala-Version sowohl parallel als auch parallel, einfach nur zum Spaß: (In meinem Dual-Core-Computing, die Parallel-Version dauert 335ms, während die No-Parallel-Version 655ms dauert)

 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: Laut Emil Hs Vorschlag habe ich meinen Code geändert, um die Effekte von IO und jvm warmup zu vermeiden:

Das Ergebnis zeigt in meinem Rechnen:

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) } } 

Kümmern Sie sich nicht um die Benchmarks; Das Problem hat mich interessiert und ich habe einige schnelle Tweaks gemacht. Dies nutzt den lru_cache Dekorateur, der eine Funktion memoisiert; is_prime(i-6) wenn wir is_prime(i-6) wir grundsätzlich diese Prime Check kostenlos. Diese Veränderung schneidet die Arbeit etwa halb. Auch können wir die range() -Aufrufe durch die ungeraden Zahlen, schneiden die Arbeit etwa in der Hälfte wieder zu machen.

http://de.wikipedia.org/wiki/Memoisierung

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

Dies erfordert Python 3.2 oder neuer, um lru_cache zu bekommen, aber könnte mit einem älteren Python arbeiten, wenn Sie ein Python-Rezept installieren, das lru_cache . Wenn du Python 2.x benutzt hast, solltest du stattdessen xrange() anstelle von 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))) 

Die oben genannten nahm nur eine sehr kurze Zeit zu bearbeiten. Ich beschloss, es noch einen Schritt weiter zu machen, und mache die Primes-Test nur versuchen, Prime Divisoren, und nur bis zu der Quadratwurzel der Nummer getestet werden. Die Art, wie ich es tat, funktioniert nur, wenn man die Nummern in der Reihenfolge überprüft, also kann es alle Primzahlen akkumulieren, wie es geht; Aber dieses Problem war bereits die Überprüfung der Zahlen in Ordnung, so dass war in Ordnung.

Auf meinem Laptop (nichts Besonderes, Prozessor ist ein 1,5 GHz AMD Turion II "K625") Diese Version produzierte eine Antwort für 100K in unter 8 Sekunden.

 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))) 

Der obige Code ist ziemlich einfach, in Python, Ruby, etc. zu schreiben, aber wäre eher ein Schmerz in C.

Sie können die Zahlen auf dieser Version nicht mit den Zahlen aus den anderen Versionen vergleichen, ohne die anderen neu zu schreiben, um ähnliche Tricks zu verwenden. Ich versuche hier nichts zu beweisen. Ich dachte nur, dass das Problem lustig war und ich wollte sehen, welche Art von leichten Leistungsverbesserungen ich nachholen könnte.

Vergiss nicht Fortran! (Meistens scherzen, aber ich würde eine ähnliche Leistung zu C erwarten). Die Aussagen mit Ausrufezeichen sind optional, aber guter Stil. ( ! Ist ein Kommentar Charakter in 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 

Ich konnte nicht widerstehen, ein paar der offensichtlichsten Optimierungen für die C-Version zu machen, die den 100k-Test nun auf 0,3s auf meinem Rechner gemacht hat (5 mal schneller als die C-Version in der Frage, die beide mit MSVC 2010 / Ox kompiliert wurden) .

 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 ); } 

Hier ist die identische Implementierung in 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" ); } } 

Mit Java 1.7.0_04 läuft das fast genau so schnell wie die C-Version. Client oder Server VM zeigt nicht viel Unterschied, außer dass JIT Training scheint der Server VM ein bisschen (~ 3%), während es fast keine Wirkung mit dem Client VM hat. Die Ausgabe in Java scheint langsamer zu sein als in C. Wenn die Ausgabe in beiden Versionen durch einen statischen Zähler ersetzt wird, läuft die Java-Version etwas schneller als die C-Version.

Das sind meine Zeiten für den 100k Run:

  • 319ms C kompiliert mit / Ox und Ausgabe an> NIL:
  • 312ms C kompiliert mit / Ox und statischen Zähler
  • 324ms Java Client VM mit Ausgabe an> NIL:
  • 299ms Java Client VM mit statischen Zähler

Und die 1M laufen (16386 Ergebnisse):

  • 24,95s C kompiliert mit / Ox und statischen Zähler
  • 25.08s Java Client VM mit statischen Zähler
  • 24.86s Java Server VM mit statischen Zähler

Während dies nicht wirklich Ihre Fragen beantworten, zeigt es, dass kleine Tweaks einen bemerkenswerten Einfluss auf die Leistung haben können. Um also Sprachen wirklich vergleichen zu können, sollten Sie versuchen, alle algorithmischen Unterschiede so weit wie möglich zu vermeiden.

Es gibt auch einen Hinweis, warum Scala scheint ziemlich schnell. Es läuft auf der Java VM und profitiert so von seiner beeindruckenden Leistung.

In Scala versuchen Sie es mit Tuple2 anstelle von List, es sollte schneller gehen. Entfernen Sie einfach das Wort 'Liste', da (x, y) ein Tuple2 ist.

Tuple2 ist spezialisiert auf Int, Long und Double bedeutet, dass es nicht müssen, um diese rohen Datentypen box / unbox. Tuple2 Quelle . Liste ist nicht spezialisiert. Liste Quelle .

Hier ist der Code für die Go (golang.org) Version:

 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) } } } 

Es lief genauso schnell wie die C-Version.

Mit einem Asus u81a Intel Core 2 Duo T6500 2.1GHz, 2MB L2 Cache, 800MHz FSB. 4GB RAM

Die 100k Version: C: 2.723s Go: 2.743s

Mit 1000000 (1M statt 100K): C: 3m35.458s Go: 3m36.259s

Aber ich denke, dass es fair wäre, Go's in Multithreading-Fähigkeiten zu nutzen und diese Version mit der regulären C-Version (ohne Multithreading) zu vergleichen, nur weil es fast zu einfach ist, Multithreading mit Go zu machen.

Update: Ich habe eine Parallelversion mit Goroutines in Go gemacht:

 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 } 

Die parallelisierte Version verwendet im Durchschnitt 2,743 Sekunden, die exakt gleiche Zeit, dass die reguläre Version verwendet.

Die parallelisierte Version wurde in 1.706 Sekunden abgeschlossen. Es verwendet weniger als 1,5 Mb RAM.

Eine seltsame Sache: Mein Dual Core Kubuntu 64bit nie in beiden Kerne spitze. Es sah aus wie Go war nur ein Kern. Fixed mit einem Aufruf zur runtime.GOMAXPROCS(4)

Update: Ich lief die paralellisierte Version bis zu 1M Zahlen. Einer meiner CPU-Kerne war zu 100% die ganze Zeit, während der andere überhaupt nicht benutzt wurde (ungerade). Es dauerte eine ganze Minute mehr als die C und die regulären Go Versionen. 🙁

Mit 1000000 (1M statt 100K):

C: 3m35.458s Go: 3m36.259s Go using goroutines: 3m27.137s 2m16.125s

Die Version 100k:

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

Basierend auf der Antwort von x4u , schrieb ich eine Scala-Version mit Rekursion, und ich verbesserte es, indem ich nur auf die sqrt statt x / 2 für die Prime Check-Funktion. Ich bekomme ~ 250ms für 100k und ~ 600ms für 1M. Ich ging voran und ging zu 10M in ~ 6s.

 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") 

Ich ging auch zurück und schrieb eine CoffeeScript (V8 JavaScript) Version, die ~ 15ms für 100k, 250ms für 1M und 6s für 10M bekommt, indem du einen Counter benutzt (ignoriert I / O). Wenn ich den Ausgang einschalte, dauert es ~ 150ms für 100k, 1s für 1M und 12s für 10M. Hätte hier keine Schwanzrekursion benutzen können, also musste ich sie wieder in Loops umwandeln.

 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") 

Nur für den Spaß daran, hier ist eine parallele Ruby-Version.

 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 

Auf meinem 1,8 GHz Core i5 MacBook Air sind die Leistungsergebnisse:

 # 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) 

Es sieht aus wie die JVM's JIT gibt Ruby eine schöne Performance-Boost in den Standard-Fall, während echte Multithreading hilft JRuby führen 50% schneller in den Thread-Fall. Was interessanter ist, dass JRuby 1.7 den JRuby 1.6-Score um 17% verbessert!

Die Antwort auf deine Frage # 1 ist das ja, die JVM ist unglaublich schnell und ja statische Schreibweise hilft.

Die JVM sollte schneller sein als C auf lange Sicht, vielleicht sogar noch schneller als "Normal" Assembler-Sprache – Natürlich können Sie immer die Hand optimieren Montage, um alles zu schlagen, indem Sie manuelle Laufzeit Profiling und Erstellen einer separaten Version für jede CPU, Sie nur Muss erstaunlich gut und kenntnisreich sein.

Die Gründe für die Geschwindigkeit von Java sind:

Die JVM kann Ihren Code analysieren, während er läuft und handoptimiert – zum Beispiel, wenn man eine Methode hat, die bei der Kompilierzeit statisch analysiert werden könnte, um eine echte Funktion zu sein, und die JVM bemerkte, dass man sie oft mit demselben aufrufte Parameter, könnte es tatsächlich den Anruf komplett beseitigen und nur die Ergebnisse aus dem letzten Anruf einspritzen (ich bin mir nicht sicher, ob Java das eigentlich genau tut, aber es tut so viele Sachen wie folgt).

Aufgrund der statischen Typisierung, kann die JVM viel über Ihren Code zum Zeitpunkt der Kompilierung wissen, dies lässt es vor-optimieren ziemlich ein bisschen Zeug. Es lässt auch den Compiler jede Klasse einzeln optimieren, ohne zu wissen, wie eine andere Klasse plant, sie zu benutzen. Auch Java hat keine willkürlichen Zeiger auf Speicherplatz, es kennt, welche Werte im Speicher und möglicherweise nicht geändert werden und können entsprechend optimieren.

Heap-Zuweisung ist viel effizienter als C, Java's Heap-Allokation ist eher wie C's Stack Zuordnung in der Geschwindigkeit – noch vielseitiger. A lot of time has gone into the different algroithims used here, it's an art–for instance, all the objects with a short lifespan (like C's stack variables) are allocated to a "known" free location (no searching for a free spot with enough space) and are all freed together in a single step (like a stack pop).

The JVM can know quirks about your CPU architecture and generate machine code specifically for a given CPU.

The JVM can speed your code long after you shipped it. Much like moving a program to a new CPU can speed it up, moving it to a new version of the JVM can also give you huge speed performances taylored to CPUs that didn't even exist when you initially compiled your code, something c physically cannot do without a recomiple.

By the way, most of the bad rep for java speed comes from the long startup time to load the JVM (Someday someone will build the JVM into the OS and this will go away!) and the fact that many developers are really bad at writing GUI code (especially threaded) which caused Java GUIs to often become unresponsive and glitchy. Simple to use languages like Java and VB have their faults amplified by the fact that the capibilities of the average programmer tends to be lower than more complicated languages.

  • Was ist der bevorzugte Weg, um 'Ertrag' in Scala zu implementieren?
  • Wie man zwei Dataframes vergleicht und zusätzliche Zeilen in einem der beiden Dataframs druckt und auch Spalten druckt, die sich in der Scala unterscheiden
  • Wie benutzt man die JDBC-Quelle zum Schreiben und Lesen von Daten in (Py) Spark?
  • Spark: Wie komme ich Python mit Scala oder Java User Defined Functions?
  • Wie benutzt man Java / Scala-Funktion aus einer Aktion oder einer Transformation?
  • Was bedeutet dieser Fehler (SimpleHttpConnectionManager wird falsch verwendet)?
  • Gibt es ein Python-Äquivalent für Scala's Option oder Entweder?
  • Filter auf der Grundlage einer anderen RDD in Spark
  • Was ist das Äquivalent zu scala.util.Try in pyspark?
  • Starten Sie HiveThriftServer programmgesteuert in Python
  • Eine Liste oder Karte als Funktionsargumente in Scala einpacken
  • Python ist die beste Programmiersprache der Welt.