F# functional style approach much slower

僤鯓⒐⒋嵵緔 提交于 2020-01-02 12:16:13

问题


Trying to learn F#, by solving some programming puzzles. I don't want to add too many details about the problem as I don't want to spoil the fun for others.

Basically, the issue is to find all 4-uples { (i,j,k,l) | i ^ j ^ k ^ l != 0 } with no repetition (eg., (1,1,1,2) and (1,1,2,1) are the same and should be counted just once).

I have found a O(n^3) approach which works, please see countImperative(a,b,c,d) below. But I also tried to refactor the code as to get rid of the nested for loops. However, I could not do so without a significant performance penalty. It was my impression that F#'s syntactic sugar would allow a more concise style (using pipes and folds), letting the compiler do the heavy-lifting to produce comparably fast code (compared to my nested for loops). The big performance hit comes from the calculation of the partial2 sum.

Here's the code:

open System
open System.Diagnostics
open System.Collections

module quadruples =
    [<EntryPoint>]
    let main argv = 
        let input = "2000 2000 2000 2000"
        let ordered = [ for x in input.Split([|' '|]) -> Convert.ToInt32(x) ] |> List.sort
        let a,b,c,d = ordered.[0], ordered.[1], ordered.[2], ordered.[3]
        let inner(a,b) = a * (a-1) / 2 + a * (b-a) 
        let sw = new Stopwatch()

        sw.Start()
        let partial1 = [ 1.. b ] |> List.fold (fun acc j -> acc + (int64 ((min a j) * inner(c-j+1, d-j+1)))) 0L
        sw.Stop()
        let elapsed1 = (sw.ElapsedMilliseconds |> double) / 1000.0
        printfn "Partial1: %f s" elapsed1

        sw.Restart()
        let combinations = [ for i in 1..a do for j in i+1..b do yield (j,i^^^j) ]
        let range = [ 1..c ]
        let partial2 = combinations |> List.fold(fun acc (j,x) -> acc + (range |> List.skip(j-1) |> List.fold(fun acc k -> if k ^^^ x < k || k ^^^ x > d then acc + 1L else acc) 0L)) 0L
        sw.Stop()
        let elapsed2 = (sw.ElapsedMilliseconds |> double) / 1000.0
        printfn "Partial2: %f s" elapsed2
        printfn "Functional: %d, Elapsed: %f s" (partial1 + partial2) (elapsed1 + elapsed2)

        // "imperative" approach
        let countImperative(a,b,c,d) =
            let mutable count = seq { 1..b } |> Seq.fold (fun acc j -> acc + (int64 ((min a j) * inner(c-j+1, d-j+1)))) 0L
            for i in 1..a do 
                for j in i+1..b do 
                    let x = i ^^^ j
                    for k in j..c do
                        let y = x ^^^ k
                        if y < k || y > d then
                            count <- count + 1L
            count

        sw.Restart();                        
        let count = countImperative(a,b,c,d)
        sw.Stop()
        printfn "Imperative: %d, Elapsed: %f s" count ((sw.ElapsedMilliseconds |> double) / 1000.0)
        0 // return an integer exit code

So my question was, if there is any way to speed up the code (specifically the calculation of partial2) while maintaining F#'s nice syntax.

来源:https://stackoverflow.com/questions/37611931/f-functional-style-approach-much-slower

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!