Geeks With Blogs

News I have joined Anti-IF Campaign
Subscribe in a reader
Lukas Domagala

I ran into some rather interesting numbers while trying to optimize my Connect Four implementation. Try to guess what this code will print out:

let test=
    let stop1 = Stopwatch.StartNew()
    let list = [1..1000000]     
    let bla = list |> List.fold (fun state x -> state + x) 0
    stop1.Stop()
    let stop2 = Stopwatch.StartNew()
    let seq = seq{1..1000000} |> Seq.fold (fun state x -> state + x) 0
    stop2.Stop()  
    let stop3 = Stopwatch.StartNew()
    let arr = [|1..1000000|]     
    let arr1 = arr|> Array.fold (fun state x -> state + x) 0
    stop3.Stop()
    let stop4 = Stopwatch.StartNew()
    let seqList = [1..1000000] |> Seq.fold (fun state x -> state + x) 0
    stop4.Stop()  
    printfn "list: %d seq: %d array: %d seqList: %d" 
            stop1.ElapsedTicks 
            stop2.ElapsedTicks 
            stop3.ElapsedTicks 
            stop4.ElapsedTicks
list: 18357402 seq: 10837883 array: 8420279 seqList: 15246287

It seems creating a list enumerable takes about double the time it takes to create either a sequence or an array. Usually this does not matter that much, but it can give you worse performance if you are using fold/map instead of manual looping. I guess there is a performance hit for simplicity…

Lets see how well we can do in the imperative world:

let test2=
    let stop1 = Stopwatch.StartNew()
    let mutable i = 1
    let mutable y = 0
    while i < 1000000 do
        y <- y+i
        i <- i+1
    stop1.Stop()
    printfn "loop: %d" stop1.ElapsedTicks
loop: 40852

Interesting, it seems most of the time is wasted creating the data structure we want to loop over, so what happens if we don’t count the creation of the array?

let test3=
    let array = [|1..1000000|] 
    let stop = Stopwatch.StartNew()
    let result = array |> Array.fold (fun state x -> state + x) 0
    stop.Stop()  
    printfn "array: %d" stop.ElapsedTicks

array: 65653

Well we are getting closer. Although we have to problem now that we have to cache the arrays we want to loop over. Lets try not being so wasteful and doing it all in a nice recursive loop:

let test4=
    let sum x=
        let rec loop x y =
            if y = 0 then x
            else loop (x+y) (y-1)
        loop 0 x
    let stop = Stopwatch.StartNew()
    let result =sum 1000000
    stop.Stop()
    printfn "recursive: %d" stop.ElapsedTicks
recursive: 28952

Nice, its faster then the imperative version. The problem with this code is that it does not state its intent at all. When i folded over the array version it took a second and i knew what the code was supposed to do. The same thing can’t be said for the imperative or the recursive version. It seems you have to sacrifice a lot of performance for readability sometimes. I´m not really looking forward to rewriting all my tight loops into these recursive monsters…

Posted on Tuesday, June 23, 2009 3:05 PM .Net , F# | Back to top


Comments on this post: Performance of Lists, Sequences and Arrays as loopcounters

No comments posted yet.
Your comment:
 (will show your gravatar)


Copyright © Domagala | Powered by: GeeksWithBlogs.net