I wrote a small set of functions for executing code while measuring
and/or limiting its time and/or memory consumption. I did this both
to run timing experiments, and to let me run things at the REPL
without risking an infinite loop (and having to restart Clojure from
scratch).
Would anyone else find these useful?
Some snippets:
user> (defn work [] (reduce + (doall (map identity (range 500000)))))
#'user/work
user> (work)
124999750000
; A simple function that does some work
user> (time (do (dotimes [_ 10] (work)) "done"))
"Elapsed time: 6929.528 msecs"
"done"
; The usual time macro
user> (get-time (do (dotimes [_ 10] (work)) "done"))
6754.874
; get-time returns the amount of time taken (in ms), and throws away
the value returned
user> (get-time-pair (do (dotimes [_ 10] (work)) "done"))
["done" 6729.438]
; get-time-pair returns both the value and the time taken
user> (time-limit (do (dotimes [_ 10] (timeout) (work)) "done") 10)
["done" 6808.941]
user> (time-limit (do (dotimes [_ 10] (timeout) (work)) "done") 5)
:timeout
; time-limit runs the first form for at most x seconds, returning the
result of get-time-pair
; if the run completed, and otherwise kills the execution of the form
and returns :timeout.
; This is implemented by running your form in a separate Future
thread.
; If your worker doesn't do any blocking IO, it should call "timeout"
periodically as you see here
; (which is fairly low-overhead). It seems impossible to get around
this limitation.
user> (time-and-memory-limit (do (dotimes [_ 10] (timeout) (work))
"done") 10 100)
["done" 6283.163]
user> (time-and-memory-limit (do (dotimes [_ 10] (timeout) (work))
"done") 10 5)
:memout
user> (time-and-memory-limit (do (dotimes [_ 10] (timeout) (work))
"done") 5 100)
:timeout
; time-and-memory-limit is like time-limit, but also imposes a heap
growth limitation (in MB).
; Memory instrumenting is tricky; the method I use is not perfect, but
should give reasonable
; numbers as long as you're not getting close to the limit (in which
case forced GCs will slow
; things down).
user> (time-and-memory-instrument (do (dotimes [_ 10] (timeout)
(work)) "done") 10 100)
["done" 6379.433 8.624679565429688]
; time-and-memory-instrument is like time-and-memory-limit, but it
also returns the max
; heap size growth (in MB). This may affect timing in a somewhat
unpredictable fashion,
; and the numbers are somewhat questionable, but it at least gives you
an idea how much
; memory your code is using.
My current code is posted here, if you're interested how it works.
http://paste.lisp.org/display/76181
Comments and suggestions welcome.
Cheers,
Jason