代码之家  ›  专栏  ›  技术社区  ›  ChaosPandion

这个素数生成器的执行时间可以改进吗?

  •  10
  • ChaosPandion  · 技术社区  · 16 年前

    我写这篇文章的最初目标是尽可能地留下最小的足迹。我可以满怀信心地说,这一目标已经实现。不幸的是,这使得我的实现相当缓慢。在3Ghz英特尔芯片上,生成200万以下的所有素数大约需要8秒。

    是否有任何方法可以以最小的内存占用牺牲来提高代码的执行时间?或者,当我从功能的角度来看这个问题时,我是不是走错了方向?

    /// 6.5s for max = 2,000,000
    let generatePrimeNumbers max =    
        let rec generate number numberSequence =
            if number * number > max then numberSequence else
            let filteredNumbers = numberSequence |> Seq.filter (fun v -> v = number || v % number <> 0L)
            let newNumberSequence = seq { for i in filteredNumbers -> i }
            let newNumber = newNumberSequence |> Seq.find (fun x -> x > number)
            generate newNumber newNumberSequence                
        generate 2L (seq { for i in 2L..max -> i })
    

    我调整了算法,设法缩短了2秒,但内存消耗增加了一倍。

    /// 5.2s for max = 2,000,000
    let generatePrimeNumbers max =    
        let rec generate number numberSequence =
            if number * number > max then numberSequence else
            let filteredNumbers = numberSequence |> Seq.filter (fun v -> v = number || v % number <> 0L) |> Seq.toArray |> Array.toSeq
            let newNumber = filteredNumbers |> Seq.find (fun v -> v > number)
            generate newNumber filteredNumbers                
        generate 2L (seq { for i in 2L..max -> i })
    

    更新

    显然,我用的是一个旧的编译器。对于最新版本,我的原始算法需要6.5秒而不是8秒。这是相当大的进步。

    5 回复  |  直到 16 年前
        1
  •  8
  •   Community Mohan Dere    9 年前

    Tomas Petricek's function 速度相当快,但我们可以让它快一点。

    比较以下各项:

    let is_prime_tomas n =
        let ms = int64(sqrt(float(n)))
        let rec isPrimeUtil(m) =
            if m > ms then true
            elif n % m = 0L then false
            else isPrimeUtil(m + 1L)
        (n > 1L) && isPrimeUtil(2L)
    
    let is_prime_juliet n =
        let maxFactor = int64(sqrt(float n))
        let rec loop testPrime tog =
            if testPrime > maxFactor then true
            elif n % testPrime = 0L then false
            else loop (testPrime + tog) (6L - tog)
        if n = 2L || n = 3L || n = 5L then true
        elif n <= 1L || n % 2L = 0L || n % 3L = 0L || n % 5L = 0L then false
        else loop 7L 4L
    

    is_prime_juliet 有一个稍微快一点的内环。这是一种众所周知的素数生成策略,它使用“切换”以2或4的增量跳过非素数。用于比较:

    > seq { 2L .. 2000000L } |> Seq.filter is_prime_tomas |> Seq.fold (fun acc _ -> acc + 1) 0;;
    Real: 00:00:03.628, CPU: 00:00:03.588, GC gen0: 0, gen1: 0, gen2: 0
    val it : int = 148933
    
    > seq { 2L .. 2000000L } |> Seq.filter is_prime_juliet |> Seq.fold (fun acc _ -> acc + 1) 0;;
    Real: 00:00:01.530, CPU: 00:00:01.513, GC gen0: 0, gen1: 0, gen2: 0
    val it : int = 148933
    

    更快 因为我们不需要过滤 2L .. 2000000L

    > let getPrimes upTo =
        seq {
            yield 2L;
            yield 3L;
            yield 5L;
            yield! (7L, 4L) |> Seq.unfold (fun (p, tog) -> if p <= upTo then Some(p, (p + tog, 6L - tog)) else None)
        }
        |> Seq.filter is_prime_juliet;;
    Real: 00:00:00.000, CPU: 00:00:00.000, GC gen0: 0, gen1: 0, gen2: 0
    
    val getPrimes : int64 -> seq<int64>
    
    > getPrimes 2000000L |> Seq.fold (fun acc _ -> acc + 1) 0;;
    Real: 00:00:01.364, CPU: 00:00:01.357, GC gen0: 36, gen1: 1, gen2: 0
    val it : int = 148933
    

    我们从1.530秒降到了01.364秒,所以速度提高了约1.21倍。令人惊叹的!

        2
  •  7
  •   Juliet    16 年前

    只是为了好玩,让我们看看 this page .

    pi(x)是素数计数函数,它返回x以下的素数。您可以使用以下不等式近似pi(x):

    (x/log x)(1 + 0.992/log x) < pi(x) < (x/log x)(1 + 1.2762/log x) 
    // The upper bound holds for all x > 1
    

    n ln n + n ln ln n - n < p(n) < n ln n + n ln ln n
    // for n >= 6
    

    有鉴于此, here is a very fast generator 计算前n个素数,其中每个元素在索引处 i 平等的 p(i) . 因此,如果我们想将数组的素数限制在2000000以下,我们使用素数计数函数的上界不等式:

    let rec is_prime (primes : int[]) i testPrime maxFactor =
        if primes.[i] > maxFactor then true
        else
            if testPrime % primes.[i] = 0 then false
            else is_prime primes (i + 1) testPrime maxFactor
    
    let rec prime_n (primes : int[]) primeCount testPrime tog =
        if primeCount < primes.Length then
            let primeCount' =
                if is_prime primes 2 testPrime (float testPrime |> sqrt |> int) then
                    primes.[primeCount] <- testPrime
                    primeCount + 1
                else
                    primeCount
            prime_n primes primeCount' (testPrime + tog) (6 - tog)
    
    let getPrimes upTo =
        let x = float upTo
        let arraySize = int <| (x / log x) * (1.0 + 1.2762 / log x)
        let primes = Array.zeroCreate (max arraySize 3)
        primes.[0] <- 2
        primes.[1] <- 3
        primes.[2] <- 5
    
        prime_n primes 3 7 4
        primes
    

    酷!那么它有多快?在我的3.2ghz四核上,我在fsi中获得以下功能:

    > let primes = getPrimes 2000000;;
    Real: 00:00:00.534, CPU: 00:00:00.546, GC gen0: 0, gen1: 0, gen2: 0
    
    val primes : int [] =
      [|2; 3; 5; 7; 11; 13; 17; 19; 23; 29; 31; 37; 41; 43; 47; 53; 59; 61; 67; 71;
        73; 79; 83; 89; 97; 101; 103; 107; 109; 113; 127; 131; 137; 139; 149; 151;
        157; 163; 167; 173; 179; 181; 191; 193; 197; 199; 211; 223; 227; 229; 233;
        239; 241; 251; 257; 263; 269; 271; 277; 281; 283; 293; 307; 311; 313; 317;
        331; 337; 347; 349; 353; 359; 367; 373; 379; 383; 389; 397; 401; 409; 419;
        421; 431; 433; 439; 443; 449; 457; 461; 463; 467; 479; 487; 491; 499; 503;
        509; 521; 523; 541; ...|]
    
    > printfn "total primes: %i. Last prime: %i" (primes.Length - 1) primes.[primes.Length - 1];;
    total primes: 149973. Last prime: 2014853
    

    因此,在不到半秒钟的时间内,所有的素数都在200万左右:)

        3
  •  4
  •   cfern    16 年前

    编辑:下面的更新版本,使用更少的内存,速度更快

    open System.Collections
    
    let getPrimes nmax =
        let sieve = new BitArray(nmax+1, true)
        let result = new ResizeArray<int>(nmax/10)
        let upper = int (sqrt (float nmax))   
        result.Add(2)
    
        let mutable n = 3
        while n <= nmax do
           if sieve.[n] then
               if n<=upper then 
                   let mutable i = n
                   while i <= nmax do sieve.[i] <- false; i <- i + n
               result.Add n
           n <- n + 2
        result
    

    上面的代码可以进一步优化:因为它只使用筛中的奇数索引,所以通过将奇数n索引为2m+1,可以将位数组的大小减少一半。新版本的速度也更快:

    let getPrimes2 nmax =
        let sieve = new BitArray((nmax/2)+1, true)
        let result = new ResizeArray<int>(nmax/10)
        let upper = int (sqrt (float nmax))   
        if nmax>1 then result.Add(2) //fixes a subtle bug for nmax < 2
    
        let mutable m = 1
        while 2*m+1 <= nmax do
           if sieve.[m] then
               let n = 2*m+1
               if n <= upper then 
                   let mutable i = m
                   while 2*i < nmax do sieve.[i] <- false; i <- i + n
               result.Add n
           m <- m + 1
        result
    

    定时(intel core duo 2.33GHz):

    > getPrimes 2000000 |> Seq.length;;
    Real: 00:00:00.037, CPU: 00:00:00.046, GC gen0: 0, gen1: 0, gen2: 0
    val it : int = 148933
    > getPrimes2 2000000 |> Seq.length;;
    Real: 00:00:00.026, CPU: 00:00:00.031, GC gen0: 0, gen1: 0, gen2: 0
    val it : int = 148933
    
        4
  •  3
  •   Tomas Petricek    16 年前

    但是,如果您想编写一个简单的功能解决方案,您可以编写以下内容:

    let isPrime(n) =
      let ms = int64(sqrt(float(n)))
      let rec isPrimeUtil(m) =
        if m > ms then true
        elif n % m = 0L then false
        else isPrimeUtil(m + 1L)
      (n > 1L) && isPrimeUtil(2L)
    
    [ 1L .. 2000000L ] |> List.filter isPrime
    

    这只是测试一个数字是否是100万的所有数字的素数。它不使用任何复杂的算法(事实上,有趣的是,最简单的解决方案往往足够好!)。在我的机器上,您的更新版本运行约11秒,这运行约2秒。

    [ 1L .. 2000000L ] |> PSeq.ofSeq |> PSeq.filter isPrime |> List.ofSeq
    

    这个 PSeq source code for Chapter 14 .

        5
  •  2
  •   Yin Zhu    16 年前

    let generatePrimes max=
        let p = Array.create (max+1) true
        let rec filter i step = 
            if i <= max then 
                p.[i] <- false
                filter (i+step) step
        {2..int (sqrt (float max))} |> Seq.map (fun i->filter (i+i) i) |> Seq.length |> ignore
        {2..max} |> Seq.filter (fun i->p.[i]) |> Seq.toArray