Eratosthenes筛选速度比较:Python与朱莉娅

时间:2014-07-30 01:37:30

标签: python performance julia

所以我对Python和Julia编写的Eratosthenes函数有一点筛子,我正在比较运行时间。

这是Python代码:

import time

def get_primes(n):
    numbers = set(range(n, 1, -1))
    primes = []
    while numbers:
        p = numbers.pop()
        primes.append(p)
        numbers.difference_update(set(range(p*2, n+1, p)))
    return primes

start = time.time()
get_primes(10000)
print time.time() - start

这是Julia代码:

function get_primes(n)
        numbers = [2:n]
        primes = Int[]
        while numbers != []
                p = numbers[1]
                push!(primes, p)
                numbers = setdiff(numbers, [p*i for i=1:int(n/p)])
        end
        return primes
end

@time get_primes(10000);

Python代码在大约.005秒内运行,而Julia代码大约需要0.5秒,因此这意味着Python运行速度提高了大约100倍。这可能是一个完全合乎逻辑的理由,但我真的不知道我在做什么。

3 个答案:

答案 0 :(得分:8)

主要的不同之处在于,在Python中你分配了一个集number,然后在适当的位置修改它,而在Julia中,你在循环的每次迭代中分配一个新数组。另一个区别是你在Python中使用了一个集合,在Julia中使用了一个数组(Python称之为“列表”)。让我们更改Julia代码以消除这两个差异:

function get_primes(n)
    numbers = IntSet(2:n)
    primes = Int[]
    while !isempty(numbers)
        p = shift!(numbers)
        push!(primes, p)
        setdiff!(numbers, p:p:n)
    end
    return primes
end

通过此更改,在我的系统上,Julia代码比Python代码快10倍(0.0005对0.0048秒)。请注意,我还使用了一个范围作为setdiff!的第二个参数 - 它比构造一个具有理解力的数组更简洁,更快(1.5倍)。

在朱莉娅写一个更惯用的写作方式是使用一系列布尔值,打开和关闭它们:

function eratosthenes(n)
    primes = fill(true,n)
    primes[1] = false
    for p = 2:n
        primes[p] || continue
        for i = 2:div(n,p)
            primes[p*i] = false
        end
    end
    find(primes)
end

最后的find返回向量非零(即为真)的索引。在我的机器上,这比其他Julia版本快5倍(0.0001秒),比Python版本快45倍。

答案 1 :(得分:2)

尝试使用setdiff!而不是setdiff

使用此代码

function get_primes(n)
   numbers::Set{Int} = Set(2:n)
   primes::Array{Int64,1} = []
   while !isempty(numbers) 
      p = minimum(numbers)
      push!(primes,p);
      setdiff!(numbers,Set(p:p:n))
   end
   return primes
end

我得到了

julia> @time get_primes(10000);
elapsed time: 0.029556727 seconds (1656448 bytes allocated)

另一个(原始)版本确实很糟糕,因为它在计算后大部分时间花在setdiff re-hashing上 - 我的未更改版本的时间与OP类似。所以看起来 setdiff!可能比 setdiff 快100倍,但只接受 而不是 < EM>阵列

这仍然比python慢​​6倍,但比使用 setdiff 快13倍。但是,如果有某种方法来维护有序集并始终采用第一个元素,那么它可能会快得多,因为几乎90%的时间(209/235)用于查找集合的最小值。

235 task.jl; anonymous; line: 96
 235 REPL.jl; eval_user_input; line: 54
  235 profile.jl; anonymous; line: 14
   209 /Users/jeffw/src/errata/julia/sive.jl; get_primes; line: 5
    2   reduce.jl; mapfoldl; line: 75
     2 dict.jl; skip_deleted; line: 669
    207 reduce.jl; mapfoldl; line: 81
     1   reduce.jl; mapfoldl_impl; line: 54
      1 dict.jl; skip_deleted; line: 670
     199 reduce.jl; mapfoldl_impl; line: 57
      10  dict.jl; skip_deleted; line: 668
      132 dict.jl; skip_deleted; line: 669
      12  dict.jl; skip_deleted; line: 670
      27  dict.jl; skip_deleted; line: 672
     7   reduce.jl; mapfoldl_impl; line: 58
   1   /Users/jeffw/src/errata/julia/sive.jl; get_primes; line: 6
   25  /Users/jeffw/src/errata/julia/sive.jl; get_primes; line: 7
    14 set.jl; setdiff!; line: 24
     1 dict.jl; skip_deleted; line: 669

更新更改为使用IntSet并切换!

function get_primes(n)
   numbers::IntSet = IntSet(2:n)
   primes::Array{Int64,1} = []
   while !isempty(numbers)
      p = shift!(numbers)
      push!(primes,p);
      setdiff!(numbers,Set(p:p:n))
   end
   return primes 
end

julia> @time get_primes(10000);
elapsed time: 0.003691856 seconds (1463152 bytes allocated)

答案 2 :(得分:2)

我测试了很多方法,我发现这个方法在8种不同的测试中是最快的。

# Julia 0.4.0 [Execution time = 95us (After warm up!!)]
function get_primes(n::Int64)
  numbers = fill(true,n)
  numbers[1] = false

  for i = 2:isqrt(n)
    if numbers[i]
      for j = i:div(n,i)
        numbers[i*j] = false
      end
    end
  end

  primes = find(numbers) # t=3-5us
  return primes
end

@time primes = get_primes(10000)
println(get_primes(100))

这个页面上的第一个Julia代码在我的电脑上计算n = 10000,大约 1'000'000us ,而这个代码大约 95us ,速度提高了10'000倍。

# Python 3.4 [Execution time = 5ms (Every time)]
def get_primes(n):
  m = n+1
  numbers = [True for i in range(m)]
  for i in range(2, int(math.sqrt(n))):
    if numbers[i]:
      for j in range(i*i, m, i):
        numbers[j] = False
  primes = []
  for i in range(2, m):
    if numbers[i]:
      primes.append(i)
  return primes

start = time.time()
primes = get_primes(10000)
print(time.time() - start)
print(get_primes(100))

Python测试

# Python n=100e6 [Execution time 52.906s]
start = time.time()
get_primes(int(100e6))
print(time.time() - start)

朱莉娅测试

# Julia n=100e6 [Execution time 3.694s]
@time get_primes(convert(Int64,100e6))

差异现在减少了。 Julia vs Python的速度提高了约12倍。