Limiting resources on a per-function basis?

Hi all,
I would really like to be able to set resource limits on a per-function
basis. I am envisioning something like:
limit :: l -> (a -> b) -> (a -> Maybe b)
limit lim fn = ...
which would convert a function so that it returns Nothing if a limit
(on stack, heap, time, etc.) is exceeded during evaluation of that
function. Is there any hope of being able to do this within the
existing GHC libraries?
If not, is it even possible to determine after-the-fact how much
stack or heap space a function evaluation required?
Thanks,
Jeff Newbern
--
Jeff Newbern

On Fri, Jan 02, 2004 at 10:07:05PM +1000, Jeff Newbern wrote:
I would really like to be able to set resource limits on a per-function basis. I am envisioning something like:
limit :: l -> (a -> b) -> (a -> Maybe b) limit lim fn = ...
which would convert a function so that it returns Nothing if a limit (on stack, heap, time, etc.) is exceeded during evaluation of that function. Is there any hope of being able to do this within the existing GHC libraries?
I am by no means a Haskell expert myself, but I did look into this issue for some time as well... If this was possible (say, with respect to time), then the compiler would be able to determine whether your functions terminate, or not. This, however, is rarely ever possible. Also, space consumption is hard to predict in face of general recursive structures, because you don't know how deep down the call sequence your function has to go. Sure, you could constrain your recursions, e.g. to tail recursion only, but for the general case, the answer for your question would be "no, it's not possible". There are, however, many approaches to overcome (or, at least address) these sorts of problems in functional programming. For instance, look at www.hume-lang.org, or look at synchronous dataflow languages which incorporate aspects of functional programming (e.g. Lucid Synchrone). I think, this field is pretty much a research area at the moment, but very much worth dealing with. Corrections to the above, however, are more than welcome. -- (o_ Andreas Bauer, baueran at in.tum.de, http://home.in.tum.de/baueran/ //\ "I have made this letter longer than usual because I lack the time V_/_ to make it shorter." -- Blaise Pascal

Andreas Bauer wrote:
I would really like to be able to set resource limits on a per-function basis. I am envisioning something like:
limit :: l -> (a -> b) -> (a -> Maybe b) limit lim fn = ...
which would convert a function so that it returns Nothing if a limit (on stack, heap, time, etc.) is exceeded during evaluation of that function. Is there any hope of being able to do this within the existing GHC libraries?
I am by no means a Haskell expert myself, but I did look into this issue for some time as well...
If this was possible (say, with respect to time), then the compiler would be able to determine whether your functions terminate, or not. This, however, is rarely ever possible. Also, space consumption is hard to predict in face of general recursive structures, because you don't know how deep down the call sequence your function has to go. Sure, you could constrain your recursions, e.g. to tail recursion only, but for the general case, the answer for your question would be "no, it's not possible".
I don't think that he was interested in predicting in advance whether
the function could be computed within the limits, but simply in being
able to abort the evaluation at the point that a limit was reached,
analogous to the way that an OS implements resource limits for
processes.
Of course, the problem with this is lazy evaluation. In a strict
language, it would be relatively straightforward, assuming that you
can actually measure the resource consumption. In a lazy language, you
have to think about what "evaluation" really means.
Ultimately, I suspect that the inability to produce a sensible
definition would render any implementation issues moot.
--
Glynn Clements

I would really like to be able to set resource limits on a per-function basis. I am envisioning something like:
limit :: l -> (a -> b) -> (a -> Maybe b) limit lim fn = ...
which would convert a function so that it returns Nothing if a limit (on stack, heap, time, etc.) is exceeded during evaluation of that function. Is there any hope of being able to do this within the existing GHC libraries?
This is tricky in a lazy language for two reasons: 1) Suppose the function returns '(42,37)', what do you want the limit to be applied to? a) Computing the whole result. b) Computing '(42,?)' (i.e., not evaluating the 2nd field) c) Computing '(?,37)' (i.e., not evaluating the 1st field) d) Computing '(?,?)' (i.e., not evaluating either field) 2) Suppose the value being evaluated depends on a shared result, should you include that cost in the result? For example, if you have: primes :: [Int] primes = ... getPrime :: Int -> Int getPrime n = primes !! n should all calls to 'limit 10 getPrime 100' return the same result even though the first call is vastly more expensive than the subsequent calls. There are also some semantic issues like retaining referential transparency but the details depend a bit on the answers to the above questions. Some of the semantic issues can be addressed using non-deterministic exceptions. (For the most part, this involves putting 'limit' in the IO monad.)
If not, is it even possible to determine after-the-fact how much stack or heap space a function evaluation required?
GHC's profiling tools can give information of this sort. I don't think there's a way to access this information at runtime but it should be possible to implement and might even be straightforward? -- Alastair Reid www.haskell-consulting.com

Alastair, Thanks for your input. I am mainly interested in this functionality to enhance my unit tests. I want to be able to run test cases with limits on time, heap, stack, etc. and fail the test if it exceeds the limits. I think for this application it is acceptable for limit to introduce a strictness requirement on the output of the function-under-test. I don't see any way around it, really. I am not sure how to address the issue of shared results, though. But since my main motivation is to enforce an upper-bound on resource usage, it is not critical that I solve this issue unless I want to apply tight bounds to an amortized algorithm. It would be nice to be able to do that, but it isn't my immediate concern. Here is my proof-of-concept version of limit that implements a timeout:
-- we use this as our dynamic exception type data TimeOut = TimeOut deriving Typeable;
-- this function waits a specified number of microseconds before -- throwing a TimeOut exception to the specified thread timer :: Int -> ThreadId -> IO () timer delay id = do threadDelay delay throwDynTo id TimeOut
-- convert a regular function into a function with a timeout, -- by forking a separate thread to wait the specified amount of time -- and then send a TimeOut exception back to the computing thread. -- if the computing thread has finished, it will kill the timer thread -- before it has a chance to raise the exception. limit :: (DeepSeq b) => Int -> (a -> b) -> (a -> IO (Maybe b)) limit lim fn = (\x -> do id <- myThreadId tid <- forkIO $ timer lim id result <- return $!! (fn x) killThread tid return $ Just result `catchDyn` handleTimeout) where handleTimeout :: TimeOut -> IO (Maybe b) handleTimeout _ = return Nothing
I would like to find a cleaner way to do this, and one that imposes fewer restrictions on the function-under-test. Also, I think implementing limits for heap and stack allocation will require a different strategy, and will undoubtedly be more difficult. I will have to become more familiar with the GHC RTS, I think... Thanks again, Jeff Newbern On Sat, 2004-01-03 at 02:33, Alastair Reid wrote:
I would really like to be able to set resource limits on a per-function basis. I am envisioning something like:
limit :: l -> (a -> b) -> (a -> Maybe b) limit lim fn = ...
which would convert a function so that it returns Nothing if a limit (on stack, heap, time, etc.) is exceeded during evaluation of that function. Is there any hope of being able to do this within the existing GHC libraries?
This is tricky in a lazy language for two reasons:
1) Suppose the function returns '(42,37)', what do you want the limit to be applied to?
a) Computing the whole result. b) Computing '(42,?)' (i.e., not evaluating the 2nd field) c) Computing '(?,37)' (i.e., not evaluating the 1st field) d) Computing '(?,?)' (i.e., not evaluating either field)
2) Suppose the value being evaluated depends on a shared result, should you include that cost in the result?
For example, if you have:
primes :: [Int] primes = ...
getPrime :: Int -> Int getPrime n = primes !! n
should all calls to 'limit 10 getPrime 100' return the same result even though the first call is vastly more expensive than the subsequent calls.
There are also some semantic issues like retaining referential transparency but the details depend a bit on the answers to the above questions.
Some of the semantic issues can be addressed using non-deterministic exceptions. (For the most part, this involves putting 'limit' in the IO monad.)
If not, is it even possible to determine after-the-fact how much stack or heap space a function evaluation required?
GHC's profiling tools can give information of this sort. I don't think there's a way to access this information at runtime but it should be possible to implement and might even be straightforward?
-- Alastair Reid www.haskell-consulting.com -- Jeff Newbern
Nomaware, Inc.

Jeff Newbern
Thanks for your input. I am mainly interested in this functionality to enhance my unit tests. I want to be able to run test cases with limits on time, heap, stack, etc. and fail the test if it exceeds the limits.
Well, if you can isolate the tests well enough (i.e. not run too many other parts of your program), you could possibly get by with GHC's options for limiting resources (for the whole program)? In particular the +RTS -K and -M options would be useful, see http://www.haskell.org/ghc/docs/6.2/html/users_guide/runtime-control.html#RT... for details. -kzm -- If I haven't seen further, it is by standing in the footprints of giants
participants (5)
-
Alastair Reid
-
baueran@t-online.de
-
Glynn Clements
-
Jeff Newbern
-
Ketil Malde