1. ホーム
  2. python

[解決済み] C、Clojure、Python、Ruby、Scalaなどのベンチマークを解釈する [終了しました]。

2023-03-20 01:18:37

質問

免責事項

私は、人工的なベンチマークが邪道であることを知っています。彼らは非常に特定の狭い状況に対してのみ結果を示すことができます。私はいくつかの愚かなベンチのために、1つの言語が他よりも優れていると仮定しません。しかし、なぜこれほどまでに結果が違うのでしょうか。一番下にある私の質問を参照してください。

数学ベンチマークの説明

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

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

質問

  1. なぜScalaは速いのですか?それは 静的型付け ? それともJVMを非常に効率的に使っているだけなのでしょうか?
  2. なぜRubyとPythonはこんなに違うのでしょうか?私はこれらの2つがやや全く異なるものではないと思っていました。私のコードが間違っているのかもしれません。ご教示ください。ありがとうございます。 UPD はい、それは私のコードの誤りでした。PythonとRuby 1.9はかなり同等です。
  3. Ruby のバージョン間の生産性では本当に印象的なジャンプです。
  4. 型宣言を追加することで、Clojureコードを最適化することができますか?それは役に立ちますか?

どのように解決するのですか?

大まかな答えです。

  1. Scalaの静的型付けは、ここでかなり役に立っています。これは、あまり余分な努力をせずに、JVMをかなり効率的に使用することを意味します。
  2. Ruby と Python の違いについては正確にはわかりませんが、おそらく (2...n).all? という関数の中で is-prime? はRubyでかなりうまく最適化されているようです(EDIT: 実際そうなっているようです、詳しくは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は10kを0.635秒で完了するようになりました(つまり、Scalaに勝って、リストの2番目に速いということです)。

P.S. のような関数を使用している場合は特に、結果を歪めてしまうので良いアイデアではありません。 print のような関数を初めて使用する場合、特に IO サブシステムの初期化などを引き起こすため、結果を歪めてしまいます!