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)

結果表

表: 計算時間 (秒 )

  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] - どれくらい時間がかかるか想像がつきません

コード一覧

子:

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"

スカラ:

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

ScalaisPrimeの最適化 (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

クロージャ:

(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 コードを最適化できますか? それは役に立ちますか?
4

13 に答える 13

30

大まかな答え:

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

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

(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 サブシステムなどの初期化が発生する場合は、結果が歪むため、良い考えではありません。

于 2012-07-25T01:12:14.563 に答える
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 の新しい reducers ライブラリを利用するバージョンを次に示します (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 です。

于 2012-07-25T05:19:04.393 に答える
22

私がリモートでインテリジェントに言うことができるのはこれだけなので、#2だけに答えますが、Pythonコードの場合は、で中間リストを作成しますが、Rubyでis_prime使用しているのは繰り返します。.mapall

次のように変更した場合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の場合は0.3秒、100Kの場合は21秒になります。

于 2012-07-25T00:35:12.037 に答える
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 内包表記とループを最適化するには?

于 2012-07-25T01:42:49.560 に答える
8

これは、楽しみのために、並列と非並列の両方での私のscalaバージョンです:(私のデュアルコアコンピューティングでは、並列バージョンは335ミリ秒かかり、非並列バージョンは655ミリ秒かかります)

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)
  }
}
于 2012-07-25T06:47:31.273 に答える
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_cachelru_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 ではさらに面倒です。

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

于 2012-07-25T01:53:36.257 に答える
7

フォートランを忘れないでください!(ほとんど冗談ですが、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
于 2012-07-25T12:01:34.773 に答える
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 でコンパイルされた 319 ミリ秒の C と >NIL への出力:
  • /Ox および静的カウンターを使用してコンパイルされた 312 ミリ秒の C
  • >NIL への出力を含む 324 ミリ秒の Java クライアント VM:
  • 静的カウンターを備えた 299 ミリ秒の Java クライアント VM

および 1M の実行 (16386 件の結果):

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

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

また、Scala がかなり高速に見える理由のヒントも得られます。Java VM 上で実行されるため、その優れたパフォーマンスを利用できます。

于 2012-07-25T14:39:36.193 に答える
4

Scalaでは、Listの代わりにTuple2を使用してみてください。高速になるはずです。(x、y)はTuple2であるため、「リスト」という単語を削除するだけです。

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

于 2012-07-25T00:45:19.577 に答える
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 の組み込みマルチスレッド機能を使用して、そのバージョンを通常の C バージョン (マルチスレッドなし) と比較するのは公平だと思います。なぜなら、Go でマルチスレッドを行うのはほとんど簡単すぎるからです。

更新: 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
}

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

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

奇妙なことの 1 つ: 私のデュアル コア 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

于 2012-07-25T04:32:18.977 に答える
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% も大幅に改善したことです!

于 2012-07-25T13:17:00.867 に答える
3

x4uの答えに基づいて、再帰を使用してscalaバージョンを作成し、素数チェック関数としてx/2ではなくsqrtに移動するだけで改良しました。100kで約250ミリ秒、1Mで約600ミリ秒かかります。私は先に進み、6秒で10Mに行きました。

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で最大15ミリ秒、1Mで250ミリ秒、10Mで6秒を取得します。出力をオンにすると、100kの場合は約150ミリ秒、1Mの場合は1秒、10Mの場合は12秒かかります。残念ながら、ここでは末尾再帰を使用できなかったため、ループに戻す必要がありました。

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")
于 2012-07-25T19:19:30.513 に答える
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に合わせて、物理的に不可能な速度のパフォーマンスを実現できます。コンパイルなしで行います。

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

于 2012-07-25T17:44:25.043 に答える