5

i tried to code to calculate time that a function costs

list <- buildlist 10000 10000    
starttime <- getClockTime
let sortedlist = quicksort list
endtime <- getClockTime
let difftime = diffClockTimes endtime starttime

function buildlist:

buildlist :: Int -> Int -> IO [Int]
buildlist n m = do
    seed <- getStdGen
    let l = randomRs (0, m) seed
    let list = take n l
    return list

function quicksort:

quicksort [] = []
quicksort (x:xs) =
    let head = [a|a<-xs,a<=x]
        tail = [a|a<-xs,a>x]
    in quicksort head ++ [x] ++ quicksort tail

first question: when i output the difftime, it is always zero no matter how long the list is.

second one: i wonder what ">>=" in the code from Real World Haskell means.

getClockTime >>= (\(TOD sec _) -> return sec)

third: i write this to get tdSec and tdPicosec from a TimeDiff variable. is there any easier way?

time <-(\(TimeDiff _ _ _ _ _ s ps) -> return [ ( \a -> fromIntegral a :: Double ) s , ( \a -> fromIntegral a :: Double ) ps ] ) difftime
6
  • What is the value of list? Commented Apr 17, 2013 at 3:31
  • 3
    In the first bit of code, it's not actually sorting the list because the value of sortedList is never used. Why did you expect it to report a non-zero time? Commented Apr 17, 2013 at 3:33
  • 1
    Also, please don't ask multiple unrelated things in a single question. Commented Apr 17, 2013 at 3:34
  • question has been edited @Code-Guru
    – yck
    Commented Apr 17, 2013 at 3:39
  • because i want to know how long it takes for the quicksort [email protected]
    – yck
    Commented Apr 17, 2013 at 3:41

3 Answers 3

11

Question 1:

Your code does not sort the list! It simply defines the name sortedlist as being quicksort list but this wont be computed until the value is actually needed. That is lazy evaluation. We do not do extra work in this language.

Since benchmarks are extra useless work (that is the point), this makes benchmarking hard.

Your choices

  • Use seq. seq has type a -> b -> b and has the behavior of evaluating its first argument to what is called "weak head normal form". Here since you want to force the entire list you might want to use deepseq
  • Use a proper benchmarking package like criterion (prefered and easier)

Question 2:

>>= is the monadic bind operator. Here it takes an IO action of type IO a and a function a -> IO b and combines them together to make a new action of type IO b. This does the same thing as do notation. foo >>= \x -> expr is the same thing as

 do x <- foo
    expr

and in fact, do notation is just syntactic sugar for >>=

I'm not sure what is being asked in question 3--perhaps it should get its own Stackoverflow question.

1
4

difftime is always zero, because Haskell's lazy evaluation order has completely optimized away the actual sort. Nothing in your program accesses sortedlist, so it's never even computed.

As stated in another answer, you can force your program to compute sortedlist with a somewhat magical function called deepseq from Control.Deepseq. deepseq v is equivalent to id, except that it has the side-effect of forcing v to be fully evaluated.

starttime <- getClockTime
let sortedlist = quicksort list
endtime <- deepseq sortedlist getClockTime

As for your second question, yes, there's an easier way to access the fields of a TimeDiff. The field names in Data declaration second as getter functions. So, tdSec td gets the seconds of td and tdPicosec td gets the picoseconds.

2
  • 1
    this benchmarks how long it takes to print. That may actually be far longer than the task of actually sorting.
    – Philip JF
    Commented Apr 17, 2013 at 3:47
  • That's quite true, if the list isn't sufficiently large. I was hoping to avoid bringing up seq or deepseq, since they're a whole new can of worms.
    – Matt S
    Commented Apr 17, 2013 at 4:27
3

For measuring the evaluation time of pure functions, you might be interested in my Chronograph package: http://hackage.haskell.org/package/chronograph

Here's a short example of how to use it:

Prelude Data.Chronograph> :m Data.Chronograph Data.List
Prelude Data.Chronograph Data.List> let theList = reverse [1..1000]
Prelude Data.Chronograph Data.List> sum theList 
500500
Prelude Data.Chronograph Data.List> let timed = chronoNF (sort theList)
Prelude Data.Chronograph Data.List> measure timed
0.000062s
Prelude Data.Chronograph Data.List> head $ val timed
1

A few points:

  • I evaluate the sum of the original theList to force its construction and reversal. If it's not forced here, the cost of construction will be attributed to the evaluation of the expression passed into chronoNF
  • chronoNF evaluates using the same strategy as deepseq as some other answers discuss. chronograph provides other functions for different evaluation strategies. For example, we could evaluate to weak head normal form, which wouldn't actually fully sort the list.

chronograph can also measure IO expressions, but those are typically much simpler to deal with than non-IO expressions.

Not the answer you're looking for? Browse other questions tagged or ask your own question.