免责声明

我知道人工基准是邪恶的.它们只能在非常特定的狭窄情况下显示结果.我不认为一种语言比另一种语言好是因为某个愚蠢的板凳.然而,我想知道为什么结果如此不同.请看下面我的问题.

数学基准描述

基准测试是一种简单的数学计算,用来找出相差6(所谓的sexy primes)的素数对

结果表

In table: calculation time in seconds

  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

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

Ruby :

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

Scala优化了isPrime(与Clojure优化中的 idea 相同):

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这么快?是因为static typing吗?或者只是非常有效地使用JVM?
  2. Why such a huge difference between Ruby and python? I thought these two are not somewhat totally different. Maybe my code is wrong. Please enlighten me! Thanks. UPD Yes, that was error in my code. python and Ruby 1.9 are pretty equal.
  3. Ruby版本之间的生产力有了惊人的提升.
  4. 我可以通过添加类型声明来优化Clojure代码吗?会有帮助吗?

推荐答案

粗略回答:

  1. Scala的静态类型对它有很大帮助——这意味着它可以非常高效地使用JVM,而无需付出太多额外的努力.
  2. 我不太确定Ruby/Python的区别,但我怀疑is-prime?函数中的(2...n).all?可能在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秒的速度完成了10公里(也就是说,在你的名单上排名第二,击败Scala)

P.S.请注意,在某些情况下,在基准测试中打印代码不是一个好主意,因为这会扭曲结果,尤其是如果首次使用print之类的函数会导致IO子系统初始化或诸如此类的事情!

Ruby相关问答推荐

有没有办法把条件语句写得更干净?

这个#divmod 方法输出这个结果是做什么的?

Integer(value) 和 value.to_i 之间的区别

在 Ruby 早期转义 .each { } 迭代

如何在 ruby​​ 中编写负循环,例如 for(i=index; i >= 0; i --)

如何使用 yardoc 列出未记录的模块/类/常量/方法?

为什么在Ruby中用空格分隔的两个字符串连接在一起?

如何使用 RVM 重新编译 ruby​​?

如何判断我是从 JRuby 还是 Ruby 运行?

如何仅从 Gemfile 中查看依赖关系树?

就地修改 ruby​​ 哈希(rails strong params)

Ruby注入初始为哈希

在 Ruby 中取消定义变量

Bundle不适用于 rbenv

Ruby 将标题发布到 slug

在命名包含多个单词的Ruby 时,是否应该使用破折号或下划线?

为什么 __FILE__ 大写而 __dir__ 小写?

如果公司使用 C++、C# 或 Java 作为应用程序语言,为什么要学习 Perl、Python、Ruby?

Ruby 是否有像栈、队列、链表、映射或集合这样的容器?

JavaScript Array:获取项目的范围