C、Clojure、Python、Ruby、Scalaなどのベンチマークの解釈[終了]


91

免責事項

私は人工的なベンチマークが悪であることを知っています。彼らは非常に特定の狭い状況でのみ結果を表示できます。いくつかの愚かなベンチのために、私はある言語が他の言語よりも優れているとは思いません。しかし、なぜ結果がそんなに違うのかしら。下部にある私の質問をご覧ください。

数学ベンチマークの説明

ベンチマークは、6異なる素数のペア(いわゆるセクシーな素数)を見つけるための単純な数学計算です。たとえば、100未満のセクシーな素数は次のようになります。(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)

結果表

表:計算時間(秒) 実行中:Factorを除くすべてがVirtualBoxで実行されていました(Debian不安定なamd64ゲスト、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]-どれくらいの時間がかかるか想像できません

コードリスト

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

ルビー:

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はisPrime最適化されています(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最適化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))))))

パイソン

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

因子

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

バッシュ(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

ご質問

  1. なぜScalaはそんなに速いのですか?静的型付けが原因ですか?または、JVMを非常に効率的に使用していますか?
  2. RubyとPythonの間にこのような大きな違いがあるのはなぜですか?私はこれらの2つは多少完全に異なるわけではないと思いました。多分私のコードは間違っています。教えてください!ありがとう。 UPDはい、それは私のコードのエラーでした。PythonとRuby 1.9はほぼ同じです。
  3. Rubyのバージョン間での生産性の飛躍的な向上。
  4. 型宣言を追加してClojureコードを最適化できますか?役に立ちますか?

6
@mgilsonは実際には最大ですsqrt(n)が、計算に時間がかかる場合があります。また、Cコードは素数を見つけたときに素数を出力しますが、他の言語はそれらをリストに計算してから出力します。Cは当然のことながら最速ですが、あなたはそれをより速く得ることができるかもしれません。
Russ

2
(そしてもちろん、エラトステネスのふるい ..しかし、このマイクロベンチマークは、反復と数学演算のストレステストとほぼ同じです。しかし、一部はより遅延しているため、まだ公正ではありません。)

2
私はGoバージョンとCバージョン(よく似ている)の両方を実行しただけで、実際には両方で同じ速度が得られました。私は100kバージョンのみを試しました:C: 2.723s Go: 2.743s
セバスティアンGrignoli

3
sqrtこのチェックのために計算する必要はありません。あなたは正方形のを計算することができますiのようにfor (i = 2; i * i <= x; ++i) ...
ivant

3
私はあなたが最適化されたScalaの注釈を付け示唆isPrimeしてを@tailrec使用すると、末尾再帰を使用していることを確認するために、。尾の再帰を妨げる何かを誤って行うことは簡単であり、この注釈はそれが発生した場合に警告するはずです。
ダニエルC.ソブラル

回答:


30

大まかな答え:

  1. Scalaの静的型付けは、ここでかなり役立ちます-これは、余計な手間をかけずにJVMをかなり効率的に使用することを意味します。
  2. Ruby / Pythonの違いは正確にはわかりません(2...n).all?が、関数でis-prime?はRubyでかなり最適化されている可能性が高いと思います(編集:これは確かにそうです、詳細についてはJulianの回答を参照してください...)
  3. Ruby 1.9.3はより最適化されています
  4. Clojureコードは確かに大幅に高速化できます!Clojureはデフォルトで動的ですが、必要に応じて、型ヒント、プリミティブ数学などを使用してScala / pure Javaの速度に近づけることができます。

Clojureコードの最も重要な最適化はis-prime?、次のような型付きプリミティブ数学を使用することです。

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

この改善により、Clojureは0.635秒で10kを完了しました(つまり、リストで2番目に速く、Scalaを破っています)。

PSは、ベンチマーク内にコードを印刷している場合があることに注意してください。特にprint、初めてのような関数を使用してIOサブシステムなどが初期化される場合は、結果が歪むため、良い考えではありません。


2
RubyとPythonについてのビットは必ずしも本当だとは思いませんが、そうでなければ+1です..

タイピングでは測定可能な安定した結果は示されませんでしたが、新しいタイプではis-prime?2倍の改善が見られます。;)
2012

unchecked-modがあった場合、これを速くすることができませんでしたか?
ヘンデカゴン2012

1
@ヘンデカゴン-おそらく!これが現在のClojureコンパイラーによってどれだけ適切に最適化されるかは不明ですが、おそらく改善の余地があります。Clojure 1.4は、一般的にこの種のものに大きく役立ちます。1.5はおそらくさらに優れています。
mikera

1
(zero? (mod n i))より速いはずです(= (mod n i) 0)
Jonas

23

これは、同じ基本アルゴリズムを使用した高速なClojureバージョンです。

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

私のマシンでは、オリジナルよりも約20倍速く実行されます。そして、1.5の新しいレデューサーライブラリを利用するバージョンがあります(Java 7または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)))

これは、オリジナルよりも約40倍速く実行されます。私のマシンでは、1.5秒で100kです。


2
静的型付けと一緒に、unchecked-remainder-intまたはそのrem代わりに使用すると、modパフォーマンスが4倍向上します。いいね!
12

22

答えは#2だけです。これは、リモートでインテリジェントに言うことができる唯一のものだからですが、Pythonコードでは、で中間リストを作成is_prime.mapていますが、all、Rubyでいるのは、繰り返します。

に変更した場合is_prime

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

彼らは同等です。

Pythonをさらに最適化することはできますが、Rubyは、私がより多くの利点を与えたときを知るには十分ではありません(たとえば、使用xrangeすると、マシンでPythonが勝つようになりますが、使用したRubyの範囲が作成したかどうか覚えていませんメモリ内の範囲全体)。

編集:馬鹿げたことなく、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")

これはそれほど変化せず、1.5秒に設定します。また、PyPyで実行すると、10Kの場合は.3秒、100Kの場合は21秒になります。


1
ジェネレーターは、関数が最初のFalse(適切なキャッチ)を実行できるようにするため、大きな違いをもたらします。
mgilson 2012

彼らがPyPyに派手に入るのを本当に楽しみにしています...それは素晴らしいことになるでしょう。
mgilson 2012

私の答えをPyPyで実行していただけませんか?それがどれだけ速いか知りたいです。
steveha

1
あなたは反復することとxrange!私は修正し、PythonとRubyが同じ結果を表示するようになりました。
12

1
@steveha私がそうするのは、あなたが今出てPyPyを自分でダウンロードすることを約束した場合のみです:)!pypy.org/download.htmlにはすべての一般的なOS用のバイナリがあり、パッケージマネージャーには間違いなくそれが含まれています。とにかく、あなたのベンチマークについては、lru_cache2.7のランダムな実装がASで見つかると、100Kは2.3秒で実行されます。
ジュリアン

16

isPrimeメソッドを次のように変更することで、Scalaを大幅に高速化できます。

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

それほど簡潔ではありませんが、プログラムは40%の時間で実行されます!

不要なRange匿名のFunction不要なオブジェクト、Scalaコンパイラが末尾再帰を認識してwhileループに変換します。これにより、JVMは多かれ少なかれ最適なマシンコードに変換できるため、Cから離れすぎてはなりません。バージョン。

参照:Scalaでfor内包表記とループを最適化する方法?


2
2倍の改善。そして素敵なリンク!
12

ところで、このメソッド本体はと同じですi == n || n % i != 0 && isPrime(n, i + 1)が、少し読みにくいですが、短くなっています
Luigi Plinge '25 / 07/25

1
@tailrec最適化が確実に行われるように、アノテーションを追加しておく必要があります。
ダニエルC.ソブラル

8

楽しみのために、並列と非並列の両方の私のscalaバージョンを以下に示します(私のデュアルコアコンピューティングでは、並列バージョンは335msかかりますが、非並列バージョンは655msです)

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

編集:Emil Hの提案に従って、IOおよびjvmウォームアップの影響を回避するためにコードを変更しました:

結果は私の計算で示しています:

リスト(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
コードはjvmウォームアップの影響を受けますか?たとえばisSexyPrime、(より)から呼び出された場合は最適化され、呼び出された場合findPrimesParはそれほど最適化されない可能性があるfindPrimes
Emil H

@EmilH十分だ。ioおよびjvmウォームアップの影響を回避するためにコードを変更しました。
Eastsun 2012

良い最適化はsqrt(n)まで上がるだけですが、今は別のアルゴリズムのベンチマークを行っています。
Luigi Plinge

7

ベンチマークを気にしないでください。問題は私を興味深くし、私はいくつかの迅速な調整を行いました。これはlru_cache関数を記憶するデコレータを使用します。電話をかけるis_prime(i-6)と、基本的に無料でプライムチェックを受けられます。この変更により、作業が約半分になります。また、range()奇数だけを呼び出して、作業をほぼ半分に減らすことができます。

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

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

これにはPython 3.2以降が必要ですがlru_cache、を提供するPythonレシピをインストールすると、古いPythonで動作する可能性がありますlru_cache。Python 2.xを使用している場合は、のxrange()代わりに実際に使用する必要があります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)))

上記は編集に非常に短い時間しかかかりませんでした。私はそれをさらに一歩進めることにし、素数検定は素数の除数のみを試し、検定される数の平方根までとします。私が行った方法は、数字を順番にチェックする場合にのみ機能するため、すべての素数を累積していくことができます。しかし、この問題はすでに順番に番号をチェックしていたので問題ありませんでした。

私のラップトップ(特別なものはありません。プロセッサは1.5 GHz AMD Turion II "K625")で、このバージョンは8秒未満で100Kの応答を生成しました。

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

上記のコードはPython、Rubyなどで書くのはかなり簡単ですが、Cではもっと面倒です。

同様のトリックを使用するように他のバージョンを書き直すことなく、このバージョンの数値を他のバージョンの数値と比較することはできません。ここでは何も証明しようとはしていません。私は問題が楽しいと思っただけで、どのような簡単なパフォーマンス改善を収集できるかを見たかったのです。


lru_cache確かに気の利いたです。連続するフィボナッチ数列の生成など、特定のクラスの問題では、関数にその1行のデコレータを追加するだけで、大幅なスピードアップが得られます。レイモンドヘッティンガーの講演へのリンクは、lru_cache約26分です 。blip.tv/ pycon
us

3
lru_cacheを使用することにより、実際には生のコードではなく別のアルゴリズムを使用します。したがって、パフォーマンスはアルゴリズムに関するものであり、言語自体に関するものではありません。
Eastsun 2012

1
@Eastsun-どういう意味かわかりません。 lru_cache最近すでに実行されている計算を繰り返すのを避け、それがすべてです。それが「実際に別のアルゴリズムを使用する」のかどうかはわかりません。そしてPythonは遅いという欠点がありますが、のようなクールなものがあることから利益を得lru_cacheます。言語の有益な部分を使用することに問題はないと思います。そして、私は言った 1が他の人に同様の変更を加えることなく、他の言語に対する私の答えの実行時間を比較してはならないこと。だから、私はあなたの意味が理解できません。
steveha

@Eastsunは正しいですが、その一方で、追加の制約が与えられない限り、より高レベルの言語の利便性が許可されるべきです。lru_cacheは速度のためにメモリを犠牲にし、アルゴリズムの複雑さを調整します。
マットジョイナー

2
別のアルゴリズムを使用する場合は、エラトステネスのふるいを試すことができます。Pythonバージョンは100Kの応答を0.03数秒(30ms)で生成しました
jfs

7

Fortranをお忘れなく!(ほとんど冗談ですが、Cと同様のパフォーマンスが期待されます)。感嘆符が付いたステートメントはオプションですが、スタイルが優れています。(!は、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

6

私はCバージョンの最も明らかな最適化のいくつかに抵抗することができませんでした。これにより、100kテストで私のマシンで0.3がかかりました(問題のCバージョンよりも5倍速く、どちらも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 );
}

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

Java 1.7.0_04では、これはCバージョンとほぼ同じ速度で実行されます。クライアントVMとサーバーVMには大きな違いはありませんが、JITトレーニングはサーバーVMを少し(3%程度)助けるようですが、クライアントVMにはほとんど影響しません。Javaでの出力はCでの出力よりも遅いようです。両方のバージョンで出力が静的カウンタに置き換えられた場合、JavaのバージョンはCのバージョンよりも少し高速に実行されます。

これらは100k実行の私の時間です:

  • / Oxでコンパイルされ、> NILに出力される319ms C:
  • / Oxおよび静的カウンターでコンパイルされた312ms C
  • > NILに出力される324ms JavaクライアントVM:
  • 静的カウンタを備えた299ms JavaクライアントVM

1M実行(16386結果):

  • / Oxと静的カウンターでコンパイルされた24.95s C
  • 25.08s静的カウンターを備えたJavaクライアントVM
  • 静的カウンターを備えた24.86s JavaサーバーVM

これは実際にあなたの質問に答えるものではありませんが、小さな調整がパフォーマンスに顕著な影響を与える可能性があることを示しています。したがって、実際に言語を比較できるようにするには、すべてのアルゴリズムの違いをできるだけ回避する必要があります。

また、Scalaがかなり高速に見える理由もわかります。Java VM上で実行されるため、その印象的なパフォーマンスの恩恵を受けます。


1
素数検査関数の場合、x >> 1の代わりにsqrt(x)に移動する方が高速です。
イブフリーマン、

4

Scalaでは、Listの代わりにTuple2を使用してみてください。これにより、処理が速くなります。(x、y)はタプル2なので、「リスト」という単語を削除するだけです。

Tuple2はInt、Long、Doubleに特化しており、これらの生データ型をボックス化/ボックス化解除する必要はありません。Tuple2ソース。リストは専門的ではありません。ソースのリスト


その後、それを呼び出すことはできませんforall。私はこれが最も効率的なコードではないかもしれないと考えました(nビューを使用するのではなく、大きな厳密なコレクションが大きいために作成されるため)さらに、それは確かに短く+エレガントであり、機能的なスタイルがたくさん。
0__

あなたの言う通り、「forAll」がそこにあると思いました。それでも、Listは大幅に改善されているはずです。これらの2つの呼び出しがあってもそれほど悪くはありません。
Tomas Lazaro

2
確かに高速で、def sexyPrimes(n: Int) = (11 to n).map(i => (i-6, i)).filter({ case (i, j) => isPrime(i) && isPrime(j) })ここでは約60%高速なので、Cコードに勝るはずです:)
0__

うーん、4〜5%のパフォーマンス向上しか得られない
ええと

1
私が見つけcollect実質的に遅いです。最初にフィルターを実行してからマップすると、より高速になります。withFilter実際には中間コレクションを作成しないため、少し高速です。(11 to n) withFilter (i => isPrime(i - 6) && isPrime(i)) map (i => (i - 6, i))
Luigi Plinge

4

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

Cバージョンと同じくらい高速に実行されました。

Asus u81a Intel Core 2 Duo T6500 2.1GHz、2MB L2キャッシュ、800MHz FSBを使用。4GB RAM

100kバージョン: C: 2.723s Go: 2.743s

1000000の場合(100Kではなく1M): C: 3m35.458s Go: 3m36.259s

しかし、Goでマルチスレッドを実行するのがほとんど簡単だからといって、Goの組み込みマルチスレッド機能を使用して、そのバージョンを通常のCバージョン(マルチスレッドなし)と比較するのは公平だと思います。

更新:GoでGoroutinesを使用してパラレルバージョンを実行しました:

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
}

並列化されたバージョンは平均2.743秒で使用され、通常のバージョンとまったく同じ時間でした。

並列化バージョンは1.706秒で完了しました。1.5 Mb未満のRAMを使用しました。

奇妙なことに、デュアルコアのkubuntu 64ビットは両方のコアでピークに達しませんでした。Goがコアを1つだけ使用していたようです。を呼び出して修正runtime.GOMAXPROCS(4)

更新:パラレル化されたバージョンを最大100万個実行しました。 CPUコアの1つは常に100%でしたが、もう1つはまったく使用されていません(奇数)。Cバージョンと通常のGoバージョンよりも1分以上かかりました。:(

1000000の場合(100Kではなく1M):

C: 3m35.458s Go: 3m36.259s Go using goroutines:3分27.137秒2m16.125s

100kバージョン:

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


ところで、いくつのコアを使用しましたか?
om-nom-nom 2012

2
Asus u81a Intel Core 2 Duo T6500 2.1GHz、2MB L2キャッシュ、800MHz FSBを持っています。4 GB RAM
セバスチャングリニョーリ

最適化を有効にしてCバージョンを実際にコンパイルしましたか?デフォルトのGoコンパイラーはインライン化せず、通常、これらの種類の比較では、最適化されたCに対してパフォーマンスが大幅に低下します。追加-O3またはより良い。
マットジョイナー

私はちょうど前に、なかった、と100Kのバージョンは-03の有無にかかわらず同じ時間を取った
セバスティアンGrignoli

1Mバージョンも同じです。おそらく、この特定の操作(非常に小さなサブセットをテストしています)は、デフォルトで適切に最適化されています。
セバスティアンGrignoli

4

ただ面白くするために、これは並列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

私の1.8GHz Core i5 MacBook Airでは、パフォーマンス結果は次のとおりです。

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

JVMのJITがデフォルトのケースでRubyに素晴らしいパフォーマンスの向上をもたらしているように見えますが、真のマルチスレッドは、スレッド化されたケースでJRubyのパフォーマンスを50%高速化するのに役立ちます。さらに興味深いのは、JRuby 1.7がJRuby 1.6スコアを健全な17%向上させたことです。


3

x4uの回答に基づいて、私は再帰を使用してscalaバージョンを作成し、プライムチェック関数のためにx / 2ではなくsqrtのみに移動することでそれを改善しました。100kの場合は〜250ms、1Mの場合は〜600msです。私は先に進み、6秒以内に1000万に進みました。

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

私も戻って、CoffeeScript(V8 JavaScript)バージョンを記述しました。これは、カウンターを使用して(I / Oを無視して)100kで最大15ms、1Mで250ms、10Mで6sを取得します。出力をオンにすると、100kで約150ms、1Mで1s、10Mで12sかかります。残念ながら、ここでは末尾再帰を使用できなかったため、ループに戻す必要がありました。

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への答えは、はい、JVMは信じられないほど高速であり、静的なタイピングは役立ちます。

JVMは、長期的にはCよりも高速である必要があります。「通常の」アセンブリ言語よりも高速である可能性があります。もちろん、手動でランタイムプロファイリングを実行し、CPUごとに個別のバージョンを作成することで、いつでもアセンブリを手動で最適化して、あらゆるものを打ち負かすことができます。驚くほど良く知識が豊富でなければなりません。

Javaの速度の理由は次のとおりです。

JVMは、実行中にコードを分析して手動で最適化できます。たとえば、コンパイル時に静的に分析して真の関数にできるメソッドがあり、JVMが同じコードで頻繁に呼び出していることに気付いた場合パラメータ、それは実際に呼び出しを完全に排除し、最後の呼び出しからの結果を注入するだけです(Javaが実際にこれを正確に行うかどうかはわかりませんが、このような多くのものはありません)。

静的型付けにより、JVMはコンパイル時にコードについて多くのことを知ることができます。これにより、JVMはかなりの量を事前に最適化できます。また、コンパイラーは、別のクラスがどのように使用することを計画しているかを知らなくても、各クラスを個別に最適化できます。また、Javaにはメモリの場所への任意のポインタがありません。メモリ内のどの値が変更される可能性があるか、または変更されないかを認識し、それに応じて最適化できます。

ヒープ割り当てはCよりもはるかに効率的です。Javaのヒープ割り当ては、速度の点でCのスタック割り当てに似ていますが、より多用途です。ここで使用されているさまざまなアルゴリズムに多くの時間が費やされてきました。それは芸術です。たとえば、寿命が短いすべてのオブジェクト(Cのスタック変数など)は、「既知の」自由な場所に割り当てられます(自由な場所を検索しない)十分なスペースがある場合)、すべてが1つのステップで一緒に解放されます(スタックポップのように)。

JVMはCPUアーキテクチャの癖を知り、特定のCPU専用のマシンコードを生成できます。

JVMは、出荷後ずっとコードを高速化できます。プログラムを新しいCPUに移動するとスピードアップできるのと同じように、JVMの新しいバージョンに移動すると、最初にコードをコンパイルしたときに存在しなかったCPUに合わせた非常に高速なパフォーマンスも得られます。 Recomipleなしで行います。

ちなみに、Java速度の悪い担当者のほとんどは、JVMをロードするための長い起動時間(いつか誰かがJVMをOSにビルドしてこれがなくなる)と、多くの開発者が本当に書くのが苦手であるという事実から来ていますGUIコード(特にスレッド化)により、Java GUIが応答しなくなり、不具合が発生することがよくありました。JavaやVBのような使いやすい言語は、平均的なプログラマーの能力がより複雑な言語よりも低くなる傾向があるという事実によって、欠点が増幅されます。


JVMがC ++で記述されている場合、JVMのヒープ割り当ては、Cが意味をなさないよりもはるかに効率的であると言います。
ダニエルC.ソブラル

5
@ DanielC.Sobral言語は、実装ほど重要ではありません。Javaの「ヒープ」実装コードは、Cのようなものではありません。Javaは、今日開発されている最先端の技術を含む研究に多くの人年の労力を費やして、さまざまなターゲットに対して高度に最適化できる交換可能なマルチステージシステムです。Cはヒープを使用しています。Javaのシステムは、Cがポインターを許可しているため、言語を変更せずに割り当てられた任意のメモリチャンクの「安全な」移動を保証できない(Cをレンダリングしない)ため、Cに実装することは不可能
Bill K

安全性は無関係です-あなたはそれがより安全であると主張しなかった、あなたはそれがより効率的であると主張しました。さらに、Cの「ヒープ」がどのように機能するかについてのコメントでの説明は、現実とは無関係です。
ダニエルC.ソブラル2012

あなたは私の「安全」の意味を誤解しているに違いありません。Javaは、メモリの任意のブロックを移動できることがわかっているため、いつでも移動できます。Cは、メモリの割り当てを参照するポインタがあるため、メモリの割り当てを最適化できません。また、ACヒープは通常、データ構造であるヒープとして実装されます。Cのようなヒープ構造で実装されていたC ++ヒープは(したがって、「ヒープ」という名前です)私は数年間C ++にチェックインしていなかったため、これは真実ではない可能性がありますが、ユーザーに割り当てられたメモリの小さなチャンクを自由に再配置します。
ビルK
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.