Profiling Pure Code

Side effects make profiling simple: at any time, you can just ask for the time now, ask for the time later, and compute the difference. For pure code, this option isn’t readily available. Fortunately, there are other methods for profiling. Performing periodic stack traces can offer a rough but representative view of where a program spends its time. However, I’m not aware of any profiling method that beats the ad-hoc flexibility of stopwatch style profiling with side effects.

Today I had an interesting, simple idea to support stopwatch style timers fully within pure code. The idea relies on opaque ‘timer’ values. Like a stopwatch, our timer can be active or paused. In either case, I can use a simple integer to represent the time values, perhaps with microsecond precision. Here’s a rough sketch of the code (in pseudo Haskell):

newtype ActiveTimer = Active !Int64 -- microseconds since epoch
newtype PausedTimer = Paused !Int64 -- active microseconds elapsed

newTimer :: PausedTimer
newTimer = Paused 0

startTimer :: PausedTimer → ActiveTimer
startTimer = unsafePerformIO . startTimerIO

stopTimer :: ActiveTimer → PausedTimer
stopTimer = unsafePerformIO . stopTimerIO

startTimerIO :: PausedTimer → IO ActiveTimer
startTimerIO (Paused n) = Active . (- n) <$> getTime

stopTimerIO :: ActiveTimer → IO PausedTimer
stopTimerIO (Active n) = Paused . (- n) <$> getTime

unsafeReadTimerIO :: PausedTimer → IO Int64
unsafeReadTimerIO (Paused n) = pure n

getTime :: IO Int64 -- microseconds since epoch

The timers are first-class plain old immutable values. But we’re restricted on how we may safely observe them. The main thing we can safely do with timers is print them as part of a Debug `trace` message.

The purely functional language I’m developing does not have unsafePerformIO. But I do have annotations to address debugging and performance issues, and support for explicit stopwatch style profiling seems a reasonable application. It is trivial for a runtime to provide a local equivalent to newTimer, startTimer, and stopTimer. Rendering resulting timers would require routing through an IO model. Or printing a debug ‘trace’ message of some sort.

Addendum: I am concerned that these timers may hinder caching and structure sharing. I could potentially handle this by simply forbidding timers from contexts where they cause trouble. But I’m reluctant to do so. I might end up favoring second-class named timers, manipulated by annotation.

This entry was posted in Language Design and tagged , , . Bookmark the permalink.

2 Responses to Profiling Pure Code

  1. kevembuangga says:

    Excuse me but rigid doctrinal adherence to PURE functional worldview carries it’s own deserved retribution: the real world is NOT pure, ALL “functional” implementations are actually impure “under the hood”, deal with it.

    • dmbarbour says:

      Computation is mechanical. Information has a spatial-temporal physical representation that is manipulated mechanically by an algorithm. This is the case whether it’s basic arithmetic, long division, or a lambda calculus.

      Yet, impurity of implementation does not detract from the benefits of functional purity. Software is the most complex class of artifacts that humans have ever developed. When a program is expressed as a pure function, when *effects* are modeled as data on a main line call-return path rather than opaque tunnels to the side of it, reasoning about, refactoring, and testing of software is greatly simplified.

      Conveniently, it is easier to use pure code within an impure context than vice versa. For example: I might model a message-passing system as a collection of named ‘actors’ (perhaps `type actor = msg → ((name,msg) list, actor)`) together with a collection of messages-in-flight. Then I could express a scheduler function to update this system in baby steps, e.g. each step delivering some of the messages. Effects could be modeled by sending messages to special names (like `stdout` or `spawn`).

      If I take that same system of actors and messages and hand it off to an ‘opaque’ runtime-provided scheduler, I would have a non-deterministic actors system with real-world effects. That is, I get from purity to impurity by abstracting and delegating part of the pure system model.

      Because we don’t necessarily delegate to an opaque runtime, adapters between different effects models are quite feasible. Layered and hierarchical systems with ad-hoc effects handlers are not only possible, they’re straightforward. Well, assuming we avoid “opaque” effects types like Haskell’s IO that can only be analyzed by the runtime. (If IO were represented as a free monad with a big GADT, we could write adapters around IO.) Anyhow, purely functional programmers ultimately get to work within whatever effects systems they care to model. All it takes is starting with purity as a base line, rather than any specific effects system.

      Starting with purity does have challenges, of course. Historically, concerns like parallelism, profiling, persistent computation, and working with larger-than-memory data are entangled with effects systems. But separation of concerns – teasing these things apart, enabling more to be achieved within pure code without relying on external effects – is IMO a worthy endeavor.

Leave a comment