Can the execution time of this prime number generator be improved?

后端 未结 5 2149
情书的邮戳
情书的邮戳 2020-12-30 09:22

My initial goal when writing this was to leave the smallest footprint possible. I can say with confidence that this goal has been met. Unfortunately, this leaves me with a r

5条回答
  •  一向
    一向 (楼主)
    2020-12-30 10:12

    Tomas Petricek's function is pretty fast, but we can make it a little faster.

    Compare the following:

    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 has a little slightly faster inner loop. Its a well-known prime-generating strategy which uses a "toggle" to skip non-primes in increments of 2 or 4. For comparison:

    > 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
    

    My version is about 2.37x faster, and its also pretty close to the speed of the fastest imperative versions. We can make it even faster because we don't need to filter the list of 2L .. 2000000L, we can use the same strategy to generate a more optimal sequence of possible primes before we apply our filter:

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

    We dropped from 1.530s to 01.364s, so we gained about 1.21x more speed. Awesome!

提交回复
热议问题