web-dev-qa-db-ja.com

C、Clojure、Python、Ruby、Scalaなどのベンチマークを解釈する

免責事項

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

数学ベンチマークの説明

ベンチマークは、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))))))

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

因子

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は非常に高速ですか?statictypingによるものですか?それともJVMを非常に効率的に使用しているだけですか?
  2. RubyとPythonの間になぜそんなに大きな違いがあるのでしょうか?これら2つはまったく違うとは思いませんでした。コードが間違っているかもしれません。教えてください!ありがとう。 [〜#〜] upd [〜#〜]はい、それは私のコードのエラーでした。 PythonとRuby 1.9はほぼ同じです。
  3. Rubyバージョン間の生産性の本当に印象的なジャンプ。
  4. 型宣言を追加してClojureコードを最適化できますか?それは役立ちますか?
91
defhlt

大まかな答え:

  1. Scalaの静的型付けはここでかなり役立ちます-これは、余分な労力をかけずにJVMをかなり効率的に使用することを意味します。
  2. Ruby/Pythonの違いについては正確にはわかりませんが、関数(2...n).all?is-prime?は、Ruby(EDIT:これは実際に当てはまります。詳細については、ジュリアンの回答を参照してください...)
  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サブシステムまたはそのようなもの!

30
mikera

同じ基本アルゴリズムを使用した高速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です。

23
Justin Kramer

#2だけをお答えします。これはリモートで言うことができる唯一のものだからです。しかし、Pythonコードの場合、is_primeで中間リストを作成しています。 Rubyのall.mapを再使用しているだけです。

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秒になります。

22
Julian

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コンパイラーは末尾再帰を認識し、それをJVMが繰り返しループに変換します多かれ少なかれ最適なマシンコードに変わる可能性があるので、Cバージョンからそれほど遠くないはずです。

参照: Scalaでfor-comprehensionsとloopsを最適化する方法?

ここに私の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)
  }
}
8
Eastsun

ベンチマークを気にしないでください。この問題に興味を持ったので、少し調整を加えました。これは、関数をメモする_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_を取得する必要がありますが、_lru_cache_を提供するPythonレシピをインストールすると、古いPythonで動作します。 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ではさらに苦痛になります。

このバージョンの番号を他のバージョンの番号と比較することはできません。他のバージョンを書き換えて、同様のトリックを使用してください。ここで何かを証明しようとはしていません。この問題は楽しいものだと思ったので、どのような簡単なパフォーマンスの改善が得られるのかを知りたかったのです。

7
steveha

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

Cバージョンの最も明らかな最適化のいくつかを実行することに抵抗できませんでした。これにより、マシンで100kテストが0.3秒かかりました(どちらもMSVC 2010/Oxでコンパイルされた、問題のCバージョンより5倍高速です) 。

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は、JITトレーニングがサーバーを支援するためにVM少し(〜3%)ですが、クライアントVMにはほとんど効果がありません。Javaの出力は、 Cの場合。出力が両方のバージョンで静的カウンタに置き換えられた場合、JavaバージョンはCバージョンよりも少し速く実行されます。

これらは、10万回実行の私の時間です。

  • / Oxでコンパイルされた319ms C> NILへの出力:
  • / Oxおよび静的カウンターでコンパイルされた312ms C
  • 324ms Java client VM> NILへの出力:
  • 299ms Java client VM静的カウンタあり

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

  • / Oxおよび静的カウンターでコンパイルされた24.95s C
  • 25.08s Java client VM静的カウンタ付き
  • 24.86s Java server VM静的カウンタ付き

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

また、Scalaがかなり速いように見える理由も示します。Java =VMパフォーマンス。

6
x4u

ただの楽しみのために、ここにパラレル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.6は健全な17%を獲得しました!

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の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 64bitが両方のコアでピークに達することはありませんでした。 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_

4

Scala Listの代わりにTuple2を使用してみてください。高速になります。(x、y)はTuple2なので、単に 'List'という単語を削除してください。

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

4
Tomas Lazaro

x4u's answer に基づいて、scala再帰を使用してバージョンを作成し、プライムチェック関数のx/2の代わりにsqrtのみを使用して改善しました。100kで最大250ms、1Mで最大600msを取得し、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で最大15ms、1Mで250ms、10Mで6sを取得します。出力をオンにすると、100kで約150ms、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")
3
Eve Freeman

あなたの質問#1に対する答えは、はい、JVMは非常に高速であり、はい静的型付けが役立つということです。

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

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

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

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

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

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

JVMは、コードを出荷してからずっと後にコードを高速化できます。プログラムを新しいCPUに移動すると速度が上がるのと同じように、新しいバージョンのJVMに移動すると、コードを最初にコンパイルしたときにさえ存在しなかったCPUに合わせた巨大な速度パフォーマンスが得られます。再確認なしで行います。

ところで、Java速度はJVMをロードするための長い起動時間に起因します(誰かがJVMをOSに組み込み、これはなくなります!)多くの開発者がGUIコード(特にスレッド化)を書くのが本当に苦手であるため、Java GUIが応答しなくなり、不具合が生じることがあります。JavaおよびVBは、平均的なプログラマの能力がより複雑な言語よりも低くなる傾向があるという事実によって、その欠点を増幅させています。

2
Bill K