C, Clojure, Python, Ruby, Scala ve diğerlerinde bir karşılaştırma ölçütü yorumlama [kapalı]


91

Feragatname

Yapay kriterlerin kötü olduğunu biliyorum. Yalnızca çok özel dar durum için sonuçlar gösterebilirler. Aptal bir sıra yüzünden bir dilin diğerinden daha iyi olduğunu düşünmüyorum. Ancak sonuçların neden bu kadar farklı olduğunu merak ediyorum. Lütfen alttaki sorularıma bakın.

Matematik karşılaştırma açıklaması

Kıyaslama, 6 farklı asal sayı çiftlerini bulmaya yönelik basit matematik hesaplamalarıdır (sözde seksi asal sayılar ). Örneğin, 100'ün altındaki seksi asal sayılar şöyle olacaktır:(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)

Sonuçlar tablosu

Tabloda: saniye cinsinden hesaplama süresi Çalışıyor: Factor hariç tümü VirtualBox'ta çalışıyordu (Debian kararsız amd64 konuğu, Windows 7 x64 ana bilgisayar) 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] - Ne kadar zaman alacağını hayal etmekten korkuyorum

Kod listeleri

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

Yakut:

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 optimize edildi isPrime(Clojure optimizasyonundaki ile aynı fikir):

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

Faktör

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

Sorular

  1. Scala neden bu kadar hızlı? Statik yazım yüzünden mi? Yoksa JVM'yi çok verimli bir şekilde mi kullanıyor?
  2. Ruby ve Python arasında neden bu kadar büyük bir fark var? Bu ikisinin tamamen farklı olmadığını düşündüm. Belki kodum yanlıştır. Lütfen beni aydınlat! Teşekkürler. UPD Evet, kodumda hata vardı. Python ve Ruby 1.9 oldukça eşittir.
  3. Ruby sürümleri arasında verimlilikte gerçekten etkileyici sıçrama.
  4. Clojure kodunu tür bildirimleri ekleyerek optimize edebilir miyim? Yardımcı olacak mı?

6
@mgilson aslında kadar sqrt(n)ama bunun hesaplanması biraz zaman alabilir. Ayrıca, C kodunuz bulduğu asal sayıları basarken, diğer dilleriniz bunları listeler halinde hesaplar ve ardından yazdırır. C şaşırtıcı olmayan bir şekilde en hızlısı olsa da, daha hızlı elde edebilirsiniz.
Russ

2
(Ve tabii ki bir Eratosthenes Elek .. ancak bu mikro kriter yineleme ve matematik işlemlerinin hemen hemen bir stres testidir biraz daha tembel olduğu gibi Ancak, bunlar hala "adil" dir..)

2
Hem Go sürümümü hem de C sürümünüzü (birbirine çok benzeyen) çalıştırdım ve pratik olarak ikisinde de aynı hıza ulaştım. Ben sadece 100k versiyonunu denedik: C: 2.723s Go: 2.743s.
Sebastián Grignoli

3
sqrtBu kontrol için hesaplamanıza gerek yok . Sen karesini hesaplayabilir igibifor (i = 2; i * i <= x; ++i) ...
ivant

3
Kuyruk özyinelemesini kullandığınızdan emin olmak için Scala'nın optimize edilmiş isPrimeile not almanızı öneririm @tailrec. Yanlışlıkla kuyruk özyinelemesini önleyen bir şey yapmak kolaydır ve bu açıklama olursa sizi uyarmalıdır.
Daniel C. Sobral

Yanıtlar:


30

Kaba cevaplar:

  1. Scala'nın statik yazımı ona burada biraz yardımcı oluyor - bu, JVM'yi çok fazla çaba harcamadan oldukça verimli bir şekilde kullandığı anlamına geliyor.
  2. Ruby / Python farkından tam olarak emin değilim, ancak (2...n).all?işlevin is-prime?Ruby'de oldukça iyi optimize edilmiş olacağından şüpheleniyorum (DÜZENLEME: gerçekten de böyle sesler, daha fazla ayrıntı için Julian'ın cevabına bakın ...)
  3. Ruby 1.9.3, çok daha iyi optimize edilmiştir
  4. Clojure kodu kesinlikle çok hızlandırılabilir! Clojure varsayılan olarak dinamik olsa da, ihtiyaç duyduğunuzda birçok durumda Scala / saf Java hızına yaklaşmak için yazım ipuçlarını, ilkel matematikleri vb. Kullanabilirsiniz.

Clojure kodundaki en önemli optimizasyon, aşağıdaki is-prime?gibi yazılan ilkel matematiği kullanmak olacaktır :

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

Bu iyileştirmeyle, Clojure'u 0.635 saniyede 10.000 tamamlayarak elde ediyorum (yani listenizdeki en hızlı ikinci, Scala'yı geçerek)

Not : Bazı durumlarda karşılaştırma ölçütünüzde baskı kodunuz olduğunu unutmayın - sonuçları bozacağından, özellikle printde ilk kez G / Ç alt sistemlerinin başlatılmasına neden oluyorsa , bu iyi bir fikir değildir !


2
Ruby ve Python hakkındaki

Yazmak ölçülebilir sabit bir sonuç göstermedi, ancak yeni is-prime?, 2 kat gelişme gösteriyor. ;)
defhlt

Kontrolsüz bir mod olsaydı bu daha hızlı yapılamaz mıydı?
Hendekagon

1
@Hendekagon - muhtemelen! mevcut Clojure derleyicisi tarafından bunun ne kadar iyi optimize edildiğinden emin değilseniz, muhtemelen iyileştirme için yer vardır. Clojure 1.4 kesinlikle bu tür şeyler için genel olarak çok yardımcı olur, 1.5 muhtemelen daha da iyi olacaktır.
mikera

1
(zero? (mod n i))daha hızlı olmalı(= (mod n i) 0)
Jonas

23

İşte aynı temel algoritmaları kullanan hızlı bir Clojure sürümü:

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

Makinemde orijinalinden yaklaşık 20 kat daha hızlı çalışıyor. Ve 1.5 sürümündeki yeni indirgeme kitaplığından yararlanan bir sürüm (Java 7 veya JSR 166 gerektirir):

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

Bu, orijinalinizden yaklaşık 40 kat daha hızlı çalışır. Benim makinemde bu 1,5 saniyede 100 bin eder.


2
Statik yazımla birlikte unchecked-remainder-intveya rembunun yerine kullanarak mod4 kat performans artışı sağlar. Güzel!
defhlt

22

Ben söylemek uzaktan akıllı bir şey var sadece biri olduğundan ben, sadece 2. cevap edersiniz ancak Python kodu için, içeri bir ara listesi oluştururken is_primekullandığınız oysa .mapiçinde seninall sadece hangi Ruby yineleniyor.

Olarak değiştirirseniz is_prime:

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

eşitler.

Python'u daha fazla optimize edebilirdim, ancak Ruby'm daha fazla avantaj verdiğimi bilecek kadar iyi değil (örneğin, xrangePython'u kullanmak makinemde kazanır, ancak kullandığınız Ruby aralığı oluşturup oluşturmadığını hatırlamıyorum hafızadaki tüm bir aralık).

DÜZENLEME: Çok saçma olmadan, Python kodunun şöyle görünmesini sağlayın:

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

Bu çok fazla değişmiyor, benim için 1.5 saniyeye koyuyor ve fazladan aptalca, PyPy ile çalıştırmak onu 10K için 0.3s ve 100K için 21s yapıyor.


1
Jeneratör burada büyük bir fark yaratıyor çünkü fonksiyonun ilk seferde kurtulmasına izin veriyor False(iyi yakalama).
mgilson

PyPy'e uyuşmalarını dört gözle bekliyorum ... Bu harika olacak.
mgilson

Cevabımı lütfen PyPy'de çalıştırır mısınız? Bunun ne kadar hızlı olacağını merak ediyorum.
steveha

1
Hem yineleyen şey hem de xrange! Düzelttim ve şimdi Python ve Ruby eşit sonuçlar gösteriyor.
defhlt

1
@steveha Bunu ancak şimdi dışarı çıkıp PyPy'yi kendiniz indirmeye söz verirseniz yapacağım :)! pypy.org/download.html , tüm yaygın işletim sistemleri için ikili dosyalara sahiptir ve paket yöneticinizde şüphesiz vardır. Her neyse, kıyaslamanıza lru_cachegelince, AS'de bulunan 2.7 için rastgele bir uygulama ile 100K, 2.3s'de çalışır.
Julian

16

isPrimeYönteminizi değiştirerek Scala'yı çok daha hızlı hale getirebilirsiniz .

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

Oldukça kısa değil ama program zamanın% 40'ında çalışıyor!

Gereksiz Rangeve anonim Functionnesneleri keseriz , Scala derleyici kuyruk özyinelemesini tanır ve onu JVM'nin az çok optimal makine koduna dönüştürebileceği bir süre döngüsüne dönüştürür, bu nedenle C'den çok uzakta olmamalıdır. versiyon.

Ayrıca bkz: Scala'da anlama ve döngüler için nasıl optimize edilir?


2
2x iyileştirme. Ve güzel bağlantı!
defhlt

btw bu yöntem vücudun özdeş olduğunu i == n || n % i != 0 && isPrime(n, i + 1)okumak biraz daha olsa, daha kısa olan,
Luigi Plinge

1
@tailrecOptimizasyonu yapmasını sağlamak için ek açıklamayı eklemelisiniz .
Daniel C. Sobral

8

İşte hem paralel hem de paralel olmayan ölçekleme sürümüm, sadece eğlence için: (Çift çekirdekli hesaplamamda paralel sürüm 335 ms, paralel olmayan sürüm 655 ms sürüyor)

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

DÜZENLEME: Emil H'nin önerisine göre, IO ve jvm ısınmasının etkilerinden kaçınmak için kodumu değiştirdim:

Sonuç hesaplamamda görünüyor:

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

1
Kod jvm ısınmasından etkileniyor mu? Örneğin isSexyPrimeçağrıldığında (diğer) optimize olabilir findPrimesParve daha aradı değilkenfindPrimes
Emil H

@EmilH Yeterince adil. İo ve jvm ısınmasının etkisini önlemek için kodumu değiştirdim.
Eastsun

Yalnızca sqrt (n) 'ye çıkmak iyi bir optimizasyondur, ancak şimdi farklı bir algoritmayı kıyaslıyorsunuz.
Luigi Plinge

7

Kriterleri boşverin; sorun beni ilgilendirdi ve bazı hızlı ayarlamalar yaptım. Bu, lru_cachebir işlevi hatırlayan dekoratör kullanır ; yani biz aradığımızdais_prime(i-6) aradığımızda temel olarak bu ana çeki ücretsiz alıyoruz. Bu değişiklik işi kabaca yarıya indiriyor. Ayrıca, range()aramaları sadece tek sayılardan geçirebiliriz ve işi kabaca tekrar yarıya indirebiliriz .

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

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

Bu, Python 3.2 veya daha yenisini gerektirir lru_cache, ancak sağlayan bir Python tarifi yüklerseniz daha eski bir Python ile çalışabilir lru_cache. Python 2.x kullanıyorsanız, xrange()bunun yerine gerçekten kullanmalısınızrange() .

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

Yukarıdakilerin düzenlenmesi çok kısa sürdü. Bir adım daha ileri götürmeye karar verdim ve asal testini yalnızca asal bölenleri deneyin ve yalnızca test edilen sayının kareköküne kadar yapmaya karar verdim. Benim yaptığım yöntem, yalnızca sayıları sırayla kontrol ederseniz işe yarar, böylece tüm asal sayıları biriktirebilir; ama bu sorun zaten sayıları sırayla kontrol ediyordu, bu yüzden sorun değildi.

Dizüstü bilgisayarımda (özel bir şey değil; işlemci 1.5 GHz AMD Turion II "K625"), bu sürüm 8 saniyenin altında 100K için bir yanıt üretti.

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

Yukarıdaki kod Python, Ruby, vb. İle yazmak oldukça kolaydır, ancak C'de daha çok acı çekecektir.

Diğerlerini benzer numaralar kullanacak şekilde yeniden yazmadan, bu sürümdeki sayıları diğer sürümlerdeki sayılarla karşılaştıramazsınız. Burada hiçbir şey kanıtlamaya çalışmıyorum; Sorunun eğlenceli olduğunu düşündüm ve ne tür kolay performans iyileştirmeleri yapabileceğimi görmek istedim.


lru_cachekesinlikle şık. Ardışık Fibonacci sayıları üretmek gibi belirli problem sınıfları için, sadece fonksiyona bir satır dekoratör ekleyerek büyük bir hızlanma sağlayabilir! İşte bir Raymond Hettinger konuşmasının bağlantısı lru_cache. Blip.tv/pycon-us-videos-2009-2010-2011/…
steveha

3
Lru_cache kullanarak, aslında ham kod yerine başka bir algoritma kullanırsınız. Yani performans algoritma ile ilgilidir, ancak dilin kendisi ile ilgili değildir.
Eastsun

1
@Eastsun - Ne demek istediğini anlamıyorum. lru_cacheyakın zamanda yapılmış bir hesaplamayı tekrarlamaktan kaçınır, hepsi bu; Bunun nasıl "aslında başka bir algoritma [ing] biz" olduğunu anlamıyorum. Ve Python yavaş olmaktan muzdariptir, ancak lru_cache; Bir dilin yararlı kısımlarını kullanmakta yanlış bir şey görmüyorum. Ve ben de cevabımın çalışma süresini diğer dillerle benzer değişiklikler yapmadan diğer dillerle karşılaştırmamamız gerektiğini söyledim . Yani, ne demek istediğini anlamıyorum.
steveha

@Eastsun haklı, ancak diğer yandan ek kısıtlamalar verilmedikçe daha yüksek seviyeli bir dilin rahatlığına izin verilmelidir. lru_cache hız için hafızayı feda eder ve algoritmik karmaşıklığı ayarlar.
Matt Joiner

2
Başka bir algoritma kullanırsanız, Eratosthenes Sieve'i deneyebilirsiniz. Python sürümü altında 100K için bir cevap üretti0.03 saniyenin ( 30ms) .
jfs

7

Fortran'ı unutma! (Çoğunlukla şaka yapıyor, ancak C'ye benzer bir performans bekliyorum). Ünlem işaretli ifadeler isteğe bağlıdır, ancak iyi bir stildir. ( !fortran 90'da bir açıklama karakteridir)

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

6

100k testinin makinemde 0.3 saniye sürmesini sağlayan C sürümü için en bariz optimizasyonlardan birkaçını yapmaya direnemedim (söz konusu C sürümünden 5 kat daha hızlı, her ikisi de MSVC 2010 / Ox ile derlendi) .

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

İşte Java'daki aynı uygulama:

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

Java 1.7.0_04 ile bu, neredeyse tam olarak C sürümü kadar hızlı çalışır. İstemci veya sunucu sanal makinesi, istemci sanal makinesiyle neredeyse hiçbir etkiye sahip olmasa da, JIT eğitiminin sunucu sanal makinesine biraz (~% 3) yardımcı olması dışında çok fazla fark göstermez. Java'daki çıktı C'dekinden daha yavaş görünüyor. Çıktı, her iki sürümde de statik bir sayaçla değiştirilirse, Java sürümü C sürümünden biraz daha hızlı çalışır.

Bunlar benim 100 bin koşu zamanlarım:

  • 319ms C / Ox ile derlenir ve> NIL'e çıktı:
  • 312ms C / Ox ve statik sayaç ile derlendi
  • > NIL çıkışlı 324 ms Java istemci VM:
  • Statik sayaçlı 299 ms Java istemci VM

ve 1M çalıştırma (16386 sonuç):

  • / Ox ve statik sayaç ile derlenmiş 24.95s C
  • Statik sayaçlı 25.08s Java istemci VM
  • Statik sayaçlı 24.86s Java sunucusu VM

Bu sorularınızı gerçekten cevaplamasa da, küçük ayarların performans üzerinde kayda değer bir etkisi olabileceğini gösteriyor. Dolayısıyla, dilleri gerçekten karşılaştırabilmek için tüm algoritmik farklılıklardan mümkün olduğunca kaçınmaya çalışmalısınız.

Ayrıca Scala'nın neden bu kadar hızlı göründüğüne dair bir ipucu veriyor. Java VM üzerinde çalışır ve bu nedenle etkileyici performansından faydalanır.


1
Asal kontrol fonksiyonu için x >> 1 yerine sqrt (x) 'e gitmek daha hızlıdır.
Eve Freeman

4

Scala'da List yerine Tuple2'yi kullanmayı deneyin, daha hızlı gitmelidir. (X, y) bir Tuple2 olduğundan 'Liste' kelimesini kaldırmanız yeterlidir.

Tuple2, Int, Long ve Double için uzmanlaşmıştır, yani bu ham veri türlerini kutuya almak / kutudan çıkarmak zorunda kalmayacaktır. Tuple2 kaynağı . Liste özel değil. Kaynak listesi .


O zaman arayamazsın forall. Ayrıca bunun en verimli kod olmayabileceğini de düşündüm (daha çok çünkü nsadece bir görünüm kullanmak yerine büyükler için sıkı bir koleksiyon oluşturuldu ), ama kesinlikle kısa + zarif ve kullanmasına rağmen ne kadar iyi performans gösterdiğine şaşırdım. çok fonksiyonel stil.
0__

Haklısın, 'Herkes için' orada olduğunu düşündüm. Yine de List üzerinde büyük bir gelişme olmalı ve bu 2 aramaya sahip olmak o kadar da kötü olmaz.
Tomas Lazaro

2
gerçekten daha hızlı, def sexyPrimes(n: Int) = (11 to n).map(i => (i-6, i)).filter({ case (i, j) => isPrime(i) && isPrime(j) })burada yaklaşık% 60 daha hızlı, bu yüzden C kodunu geçmeli :)
0__

Hmm, sadece% 4 veya% 5'lik bir performans artışı elde ediyorum
Luigi Plinge

1
Ben collectçok daha yavaş buluyorum . Daha hızlı, önce filtreyi yapıp ardından eşleştirmenizdir. withFilteraslında ara koleksiyon oluşturmadığı için biraz daha hızlıdır. (11 to n) withFilter (i => isPrime(i - 6) && isPrime(i)) map (i => (i - 6, i))
Luigi Plinge

4

İşte Go (golang.org) sürümünün kodu:

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

C versiyonu kadar hızlı çalıştı.

Asus u81a Intel Core 2 Duo T6500 2.1GHz, 2MB L2 önbellek, 800MHz FSB kullanma. 4 GB RAM

100k versiyonu: C: 2.723s Go: 2.743s

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

Ancak Go'nun yerleşik çoklu okuma yeteneklerini kullanmanın ve bu sürümü normal C sürümüyle (çoklu iş parçacığı olmadan) karşılaştırmanın adil olacağını düşünüyorum, çünkü Go ile çoklu okuma yapmak neredeyse çok kolay.

Güncelleme: Goroutines in Go kullanarak paralel bir sürüm yaptım:

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
}

Paralelleştirilmiş sürüm ortalama 2,743 saniyede kullanıldı, normal sürümle aynı süre.

Paralelleştirilmiş sürüm 1.706 saniyede tamamlandı. 1.5 Mb'den daha az RAM kullandı.

Garip bir şey: Çift çekirdekli kubuntu 64bit'im hiçbir zaman her iki çekirdekte de zirveye ulaşmadı. Görünüşe göre Go sadece bir çekirdek kullanıyordu. Bir çağrı ile düzeltildiruntime.GOMAXPROCS(4)

Güncelleme: Paralelli sürümü 1 milyon numaraya kadar çalıştırdım. CPU çekirdeklerimden biri her zaman% 100'tü, diğeri ise hiç kullanılmadı (garip). C ve normal Go sürümlerinden bir dakika daha uzun sürdü. :(

1000000 ile (100K yerine 1M):

C: 3m35.458s Go: 3m36.259s Go using goroutines:3a27.137sn2m16.125s

100k versiyonu:

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


Btw kaç tane çekirdek kullandınız?
om-nom-nom

2
Asus u81a Intel Core 2 Duo T6500 2.1GHz, 2MB L2 önbelleğim, 800MHz FSB'ye sahibim. 4 GB RAM
Sebastián Grignoli

Optimizasyonların etkin olduğu C sürümünü gerçekten derlediniz mi? Varsayılan Go derleyicisi satır içi değildir ve bu tür karşılaştırmalarda genellikle optimize edilmiş C'ye karşı büyük bir performans darbesi alır. Ekle -O3veya daha iyisi.
Matt Joiner

Sadece daha önce değil, yaptı ve 100K sürümü ile veya -O3 olmadan aynı süreyi aldı
Sebastián Grignoli

1M versiyonu için de aynı şey. Belki bu belirli işlemler (çok küçük bir alt kümeyi test ediyoruz) varsayılan olarak iyi optimize edilmiştir.
Sebastián Grignoli

4

Sırf eğlence olsun diye, işte paralel bir Ruby versiyonu.

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

1.8 GHz Core i5 MacBook Air'imde performans sonuçları:

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

Görünüşe göre JVM'nin JIT'i Ruby'ye varsayılan durumda güzel bir performans artışı sağlıyor, gerçek çoklu okuma ise JRuby'nin iş parçacıklı durumda% 50 daha hızlı çalışmasına yardımcı oluyor. Daha da ilginci JRuby 1.7'nin JRuby 1.6 puanını sağlıklı bir% 17 artırması!


3

X4u'nun cevabına dayanarak, özyinelemeyi kullanarak bir scala versiyonu yazdım ve onu asal kontrol fonksiyonu için x / 2 yerine yalnızca sqrt'ye giderek geliştirdim. 100k için ~ 250ms ve 1M için ~ 600ms alıyorum. Önden gittim ve ~ 6 saniye içinde 10 milyona gittim.

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

Ayrıca geri döndüm ve bir sayaç kullanarak (G / Ç'yi göz ardı ederek) 100k için ~ 15ms, 1M için 250ms ve 10M için 6s alan bir CoffeeScript (V8 JavaScript) sürümü yazdım. Çıkışı açarsam, 100k için ~ 150ms, 1M için 1s ve 10M için 12s sürer. Maalesef burada kuyruk özyinelemesini kullanamadım, bu yüzden onu tekrar döngülere dönüştürmek zorunda kaldım.

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

2

1. sorunuzun cevabı, Evet, JVM'nin inanılmaz derecede hızlı olması ve evet statik yazmanın yardımcı olmasıdır.

JVM, uzun vadede C'den daha hızlı, muhtemelen "Normal" montaj dilinden bile daha hızlı olmalıdır - Elbette, manuel çalışma zamanı profili oluşturarak ve her CPU için ayrı bir sürüm oluşturarak her şeyi yenmek için montajı her zaman elle optimize edebilirsiniz. inanılmaz derecede iyi ve bilgili olmalı.

Java'nın hızının nedenleri şunlardır:

JVM, kodunuzu çalışırken analiz edebilir ve elle optimize edebilir - örneğin, derleme zamanında statik olarak analiz edilebilecek bir yönteminiz varsa ve JVM, onu genellikle aynı şekilde çağırdığınızı fark ettiyse parametreler, aramayı tamamen ortadan kaldırabilir ve sadece son aramadan gelen sonuçları enjekte edebilir (Java'nın bunu tam olarak yapıp yapmadığından emin değilim, ama bunun gibi pek çok şeyi yapıyor).

Statik yazım nedeniyle, JVM derleme sırasında kodunuz hakkında çok şey bilebilir, bu da çok sayıda şeyi önceden optimize etmesine olanak tanır. Ayrıca, derleyicinin, başka bir sınıfın onu nasıl kullanmayı planladığını bilmeden her sınıfı ayrı ayrı optimize etmesini sağlar. Ayrıca Java'nın bellek konumu için keyfi işaretçileri yoktur, bellekteki hangi değerlerin değiştirilip değiştirilemeyeceğini bilir ve buna göre optimize edebilir.

Yığın tahsisi C'den ÇOK daha verimlidir, Java'nın yığın tahsisi daha çok C'nin hızlı yığın tahsisi gibidir - ancak daha çok yönlüdür. Burada kullanılan farklı algoritmalara çok zaman geçmiştir, bu bir sanattır - örneğin, kısa ömürlü tüm nesneler (C'nin yığın değişkenleri gibi) "bilinen" boş bir konuma tahsis edilmiştir (boş bir yer aranmaz yeterli alana sahip) ve hepsi tek bir adımda birlikte serbest bırakılır (bir yığın pop gibi).

JVM, CPU mimariniz hakkındaki tuhaflıkları bilir ve belirli bir CPU için özel olarak makine kodu oluşturabilir.

JVM, kodunuzu gönderdikten çok sonra bile hızlandırabilir. Bir programı yeni bir CPU'ya taşımanın onu hızlandırması gibi, onu JVM'nin yeni bir sürümüne taşımak da size kodunuzu ilk derlediğinizde bile var olmayan CPU'lara uyarlanmış devasa hız performansları verebilir, c'nin fiziksel olarak yapamayacağı bir şey tekrar etmeden yap.

Bu arada, java hızının kötü temsilcisinin çoğu, JVM'yi yüklemek için uzun başlatma süresinden (Bir gün birisi JVM'yi işletim sistemine kuracak ve bu ortadan kalkacak!) Ve birçok geliştiricinin yazma konusunda gerçekten kötü olduğu gerçeğinden geliyor. Java GUI'lerinin genellikle yanıt vermemesine ve hatalı olmasına neden olan GUI kodu (özellikle iş parçacıklı). Java ve VB gibi basit kullanımlı diller, ortalama bir programcının yeteneklerinin daha karmaşık dillerden daha düşük olma eğiliminde olması gerçeğiyle daha da artar.


JVM'nin C ++ ile yazıldığı göz önüne alındığında, JVM'nin yığın ayırmasının C'nin anlamsız olduğundan çok daha verimli olduğunu söylemek.
Daniel C. Sobral

5
@ DanielC.Sobral dili impelemntation kadar önemli değildir - Java'nın "Heap" uygulama kodu C'lere benzemez. Java'lar, günümüzde geliştirilen en son teknikler de dahil olmak üzere araştırmada uzun yıllar süren çabalarla çeşitli hedefler için son derece optomize edilebilir, değiştirilebilir çok aşamalı bir sistemdir, C bir yığın kullanır - Çağlar önce geliştirilen basit bir veri yapısı. C işaretleyicilere izin verdiği için Java'nın sistemini C için uygulamak imkansızdır, bu nedenle dil değişiklikleri olmadan keyfi ayrılmış bellek parçalarının "Güvenli" hareketlerini asla garanti edemez (artık C'yi oluşturmuyor)
Bill K

Emniyet önemsizdir - daha güvenli olduğunu iddia etmediniz, daha verimli olduğunu iddia ettiniz . Dahası, yorumdaki C "yığın" ın nasıl çalıştığını açıklamanız gerçeklikle hiçbir ilgisi yoktur.
Daniel C. Sobral

"Güvenli" kelimesinin anlamını yanlış anlamış olmalısınız - Java, herhangi bir zamanda rastgele bellek bloğunu hareket ettirebilir, çünkü yapabileceğini bilir, C, bellek bölmesini optomize edemez çünkü ona başvuran bir işaretçi olabilir. Ayrıca AC yığın genellikle bir veri yapısı olan bir yığın olarak uygulanır. C ++ yığınları eskiden C gibi yığın yapıları ile gerçeklenirdi (Bu nedenle adı "Yığın") Birkaç yıldır C ++ 'ya girmedim, bu yüzden bu artık doğru olmayabilir, ancak yine de sınırlı Kullanıcının ayrılan belleğinin küçük parçalarını isteğe göre yeniden düzenleyin.
Bill K
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.