解释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之间会有如此巨大的差异?我认为这两者并没有完全不同。也许我的代码是错误的。请赐教!谢谢。 UPD是的,那是我的代码中的错误。Python和Ruby 1.9相当。
  3. Ruby版本之间的生产力确实有了惊人的提高。
  4. 我可以通过添加类型声明来优化Clojure代码吗?有帮助吗?

6
@mgilson实际上最多,sqrt(n)但是要花一些时间才能计算出来。同样,您的C代码在找到质数时会打印出质数,而其他语言会将它们计算为列表,然后将其打印出来。毫无疑问,C是最快的,但您也许可以更快地获得它。
罗斯

2
(当然还有Eratosthenes ..但是,这个微基准测试几乎是对迭代和数学运算的压力测试。但是,它们仍然不那么“公平”,因为有些更懒。)

2
我刚运行了Go版本和C版本(看起来很相似),但实际上两者的速度相同。我只尝试了100k版本:C: 2.723s Go: 2.743s
塞巴斯蒂安·格里尼亚诺利

3
您无需sqrt为此检查进行计算。您可以计算iin 的平方for (i = 2; i * i <= x; ++i) ...
ivant 2012年

3
建议您使用注释Scala优化isPrime@tailrec,以确保您使用的是尾递归。错误地执行阻止尾部递归的操作很容易,并且如果发生这种情况,此注释应该警告您。
Daniel C. Sobral

Answers:


30

粗略的答案:

  1. Scala的静态类型在这里帮助了很多-这意味着它可以非常有效地使用JVM,而无需付出太多额外的努力。
  2. 我不确定Ruby / Python的区别,但我怀疑(2...n).all?在函数中is-prime?在Ruby可能已进行了很好的优化(编辑:听起来确实如此,请参阅Julian的答案以获取更多详细信息...)
  3. Ruby 1.9.3进行了更好的优化
  4. Clojure代码当然可以加速很多!虽然Clojure在默认情况下是动态的,但在很多情况下,您可以使用类型提示,原始数学等来接近Scala /纯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))))))

有了这一改进,我就可以在0.635秒内使Clojure完成10k的运行(即,在您的列表中排名第二快,击败Scala)

PS请注意,在某些情况下,您可能会在基准测试中打印代码-这不是一个好主意,因为这会使结果失真,尤其是print在第一次使用像这样的函数导致IO子系统或类似对象初始化的情况下!


2
我认为关于Ruby和Python的

键入并没有显示任何可测量的稳定结果,但是您的新输入is-prime?显示出2倍的改进。;)
defhlt 2012年

如果有一个unchecked-mod,这不能更快吗?
Hendekagon

1
@Hendekagon-可能!不确定当前的Clojure编译器对此优化的程度如何,可能还有改进的余地。Clojure 1.4绝对对这类东西有很大帮助,而1.5可能会更好。
mikera

1
(zero? (mod n i))应该比(= (mod n i) 0)
Jonas 2012年

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。


2
使用unchecked-remainder-intrem代替mod静态键入可以使性能提高4倍。真好!
2012年

22

我将仅回答#2,因为这是我唯一可以远程讲的东西,但是对于您的Python代码,您正在中创建一个中间列表is_prime,而.mapall在Ruby中使用的只是迭代。

如果更改is_prime为:

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

他们是平等的。

我可以进一步优化Python,但是我的Ruby不够好,无法知道何时可以提供更多优势(例如,使用xrangeMake 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.3s,100K为21s。


1
生成器在这里有很大的不同,因为它允许函数在第一个False(好捕获)时保释。
mgilson 2012年

我真的很期待他们麻木进入PyPy ...太棒了。
mgilson

您能在PyPy中运行我的答案吗?我很好奇那会快多少。
steveha

1
您对迭代事物和完全正确xrange!我已经修复了,现在Python和Ruby显示相同的结果。
2012年

1
@steveha我只有在您保证现在就出去亲自下载PyPy的情况下才这样做:)!pypy.org/download.html具有所有常见OS的二进制文件,并且您的程序包管理器无疑拥有它。无论如何,就您的基准而言,lru_cache在AS上针对2.7 进行了随机实施,100K运行于2.3s。
朱利安

16

您可以通过将isPrime方法修改为

  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中优化理解和循环?


2
提升2倍。和漂亮的链接!
2012年

顺便说一句此方法体是相同的i == n || n % i != 0 && isPrime(n, i + 1),这是短,虽然有点难以阅读
路易Plinge

1
您应该已经添加了@tailrec注释,以确保它将进行优化。
Daniel C. Sobral

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

仅提高到sqrt(n)是一个很好的优化,但是您现在正在对另一种算法进行基准测试。
路易吉·普林格

7

不用管基准测试;这个问题引起了我的兴趣,我进行了一些快速调整。这使用lru_cache装饰器来记忆一个功能;因此,当我们致电时,is_prime(i-6)我们基本上是免费获得该主要检查的。这项更改将工作减少了大约一半。此外,我们可以使range()呼叫仅遍历奇数,从而将工作大致减少一半。

http://en.wikipedia.org/wiki/备忘录

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绝对很漂亮。对于某些类别的问题,例如生成连续的斐波那契数,只需在该函数上添加该行装饰器,即可大大提高速度!这是雷蒙德· 海廷格lru_cache
us-

3
通过使用lru_cache,您实际上使用了另一种算法而不是原始代码。因此,性能与算法有关,而与语言本身无关。
Eastsun

1
@Eastsun-我不明白你的意思。 lru_cache避免重复最近已经完成的计算,仅此而已;我看不出这是“实际上是在使用另一种算法”。而且Python的运行速度很慢,但是却受益于诸如lru_cache;我认为使用语言的有益部分没有任何问题。我说过,在不对其他语言进行类似更改的情况下,不应将我的回答的运行时间与其他语言进行比较。所以,我不明白你的意思。
steveha

@Eastsun是正确的,但另一方面,除非给出其他限制,否则应该允许使用高级语言。lru_cache将牺牲内存以提高速度,并调整算法复杂度。
马特·乔纳

2
如果您使用其他算法,则可以尝试Eratosthenes筛。Python版本在0.03几秒钟内(30ms)产生了100K的答案
jfs 2012年

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差异不大,除了JIT培训似乎对服务器VM有所帮助(约3%),而对客户端VM几乎没有影响。Java中的输出似乎比C中的输出慢。如果两个版本中的输出都用静态计数器替换,则Java版本的运行速度比C版本要快一些。

这是我100k跑步的时间:

  • 319ms C用/ Ox编译并输出到> NIL:
  • 使用/ Ox和静态计数器编译的312ms C
  • 324ms Java客户端VM,输出到> NIL:
  • 带有静态计数器的299ms Java客户端VM

和1M运行(16386个结果):

  • 使用/ Ox和静态计数器编译的24.95s C
  • 25.08s带有静态计数器的Java客户端VM
  • 具有静态计数器的24.86s Java服务器VM

尽管这并不能真正回答您的问题,但它表明细微调整会对性能产生显着影响。因此,为了能够真正比较语言,您应尽量避免所有算法上的差异。

这也提示了Scala为何运行速度很快。它运行在Java VM上,因此可从其出色的性能中受益。


1
对于质数检查功能,转到sqrt(x)而不是x >> 1更快。
伊芙·弗里曼

4

在Scala中,尝试使用Tuple2代替List,它应该运行得更快。只需删除单词“列表”,因为(x,y)是一个Tuple2。

Tuple2专用于Int,Long和Double,这意味着它们不必对原始数据类型进行装箱/拆箱。Tuple2来源。列表不是专门的。列出来源


那你就不能打电话forall了。我还认为这可能不是最有效的代码(更多的是因为为大型n而不是仅使用视图创建了一个严格的大集合),但是它肯定是简短的+优雅的,尽管使用了很多功能风格。
0__

您说得对,我以为“ forAll”在那里。仍然应该在List上有一个很大的改进,拥有这两个电话并没有那么糟糕。
Tomas Lazaro 2012年

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%
Luigi Plinge 2012年

1
我发现collect速度要慢得多。如果先进行过滤然后再进行映射,则速度更快。withFilter速度稍快,因为它实际上并未创建中间集合。(11 to n) withFilter (i => isPrime(i - 6) && isPrime(i)) map (i => (i - 6, i))
路易吉·普林格

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版本一样快。

使用华硕u81a Intel Core 2 Duo T6500 2.1GHz,2MB L2缓存,800MHz FSB。4GB RAM

100k版本: C: 2.723s Go: 2.743s

1000000(1M代替100K): 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。

一件奇怪的事:我的双核kubuntu 64bit从未在两个核中达到顶峰。看起来Go只是在使用一个内核。固定为runtime.GOMAXPROCS(4)

更新:我运行的Parallellized版本最多有1M个数字。 我的一个CPU核心一直处于100%的状态,而另一个则完全没有使用(奇数)。比C和常规的Go版本花了整整一分钟的时间。:(

1000000(1M代替100K):

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

2
我有一个华硕u81a Intel Core 2 Duo T6500 2.1GHz,2MB L2缓存,800MHz FSB。4GB RAM
塞巴斯蒂安·格里尼戈利

您是否实际上在启用优化的情况下编译了C版本?默认的Go编译器不进行内联,通常会在此类比较中对优化的C产生巨大的性能影响。添加-O3或更好。
马特·乔纳

我只是做了,在此之前,和100K版本了相同的时间量有或无-O3
塞巴斯蒂安Grignoli

1M版本也是如此。默认情况下,也许可以很好地优化此特定操作(我们正在测试一个很小的子集)。
塞巴斯蒂安·格里尼戈利

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版本,并通过仅使用sqrt代替了x / 2作为质数检查功能来改进了它。对于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),它获得约100ms的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在编译时可以对您的代码有很多了解,这使它可以对一些东西进行预优化。它还使编译器可以单独优化每个类,而无需了解另一个类打算如何使用它。Java也没有指向内存位置的任意指针,它知道内存中的值可能会也可能不会更改,并且可以相应地进行优化。

堆分配比C更有效率,Java的堆分配在速度上更像C的堆分配,但用途更多。很多时间都花在了这里使用的各种算法上,这是一门艺术-例如,所有寿命短的对象(如C的堆栈变量)都分配到“已知的”自由位置(无需搜索自由点)并具有足够的空间),并且都可以在一个步骤中将它们一起释放(例如堆栈弹出)。

JVM可以了解有关您的CPU体系结构的怪癖,并专门为给定的CPU生成机器代码。

在交付代码后,JVM可以加快代码的速度。就像将程序移至新的CPU可以加快运行速度,将其移至新的JVM版本也可以为您提供巨大的速度性能,而这些性能取决于最初编译代码时甚至还不存在的CPU。不用三联。

顺便说一下,大多数Java速度不好的代表都来自于启动JVM的启动时间太长(总有一天有人会将JVM构建到OS中,这将消失!)以及许多开发人员确实不擅长编写的事实GUI代码(尤其是线程化的代码)导致Java GUI经常变得无响应和故障。Java和VB等简单易用的语言的缺点是,普通程序员的能力往往比较复杂的语言要低。


假设JVM是用C ++编写的,那么说JVM的堆分配要比C效率高得多。
Daniel C. Sobral

5
@ DanielC.Sobral语言不像实现那么重要-Java的“ Heap”实现代码与C完全不同。Java是一个可替换的多阶段系统,在许多年的研究工作中(包括当今正在开发的尖端技术),C都使用了堆-一种很久以前开发的简单数据结构,可以针对各种目标高度反对。鉴于C允许指针,因此Java的系统无法针对C实现,因此C永远无法保证任意分配的内存块的“安全”移动而无需更改语言(不再将其呈现为C)
Bill K

安全是无关紧要的-您并不是声称它更安全,而是声称它更有效。此外,您在注释中描述了C“堆”的工作方式与现实无关。
Daniel C. Sobral

您必须误解了我的“安全”的含义-Java能够随时移动任意内存块,因为它知道可以,而C无法使内存分配无效,因为可能有一个指针可以引用它。AC堆通常也实现为数据结构的堆。过去使用C之类的堆结构实现的C ++堆曾经是(因此得名“ Heap”),几年来我都没有签到C ++,所以这也许不再成立了,但是由于不能随意重新分配用户分配的内存的小块。
Bill K
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.