开发者

Updating a Big State Fast in Haskell

For my vector graphics library in Haskell I must carry around a rather big state: line stroke p开发者_StackOverflowarameters, colors, clip path etc. I know two ways of doing this. Quoting a comment from Haskell-cafe: "I would suggest you either use a reader monad with mutable state, or a state monad with immutable state".

Here is my problem: updating a big immutable state is a performance kill. Using lots of STRefs is like writing C in Haskell: it's verbose and ugly.

Here is the immutable state:

data GfxState = GfxState {
  lineWidth :: Double,
  lineCap :: Int,
  color :: Color,
  clip :: Path,
  ...
}

setLineWidth :: Double -> State GfxState ()
setLineWidth x = modify (\state -> state { lineWidth = x })

As far as I know, "state { lineWidth = x }" creates a new GfxState and lets the old one be garbage collected. This kills performance when the state is big and updated often.

Here is the mutable state:

data GfxState s = GfxState {
  lineWidth :: STRef s Double,
  lineCap   :: STRef s Int,
  color     :: STRef s Color,
  clip      :: STRef s Path,
  ...
  many more STRefs
}

setLineWidth :: GfxState s -> Double -> ST s ()
setLineWidth state x = writeSTRef (lineWidth state) x

Now I get (GfxState s) and (ST s) and (STRef s) all over the place, which is verbose, confusing, and beats the spirit of writing short and expressive code. I could use C + FFI to read and update the big state, but since I encounter this pattern quite often, I'm hoping there's a better way.


First of all I have to ask are you just claiming it's going to be slow or did you profile or at least notice a performance issue? otherwise guessing or making assumptions isn't particuarly useful. Anyway I recommend grouping your data, at the moment it looks like you're just laying out your structure completely flat when you could group related data like the data related to lines into records.

You might also want to seperate out bits that really need to be in the state monad and others that don't into a reader/writer monad and combine them using monad transformers. Regarding elegance of code I'd recommend using (first-class/higher-order) record libraries like fclabels.

I've made heavy use of state monads (in a stack of a monad transformer) in a few small projects and I haven't noticed any performance issues yet.

Lastly you could use modify instead of a get/put pairs.


Even with quite a few fields in your record, "creating a new one" just means copying pointers. And "letting the old one be garbage collected" just means releasing a few bytes for each pointer in a way that GHC's generational garbage collector is very fast at handling. It all boils down to a handful of machine instructions. So even for a graphics application, that very well may not kill your performance at all.

If you are sure that it really does impact on performance, organize the fields into a tree. You can create a fixed-shape tree by using nested data types, or even just use Data.IntMap. That will get you an average of log n / 2 pointer copies. You can do even better if you know that certain fields are accessed much more often.

It would be a very rare application whose state is so complex and whose performance requirements are so heavy that the only option is STRef fields. But it's nice to know that the option is there.


As an aside, you should certainly be improving your data type representation via unboxing, if you are concerned about performance:

data GfxState = GfxState {
  lineWidth :: {-# UNPACK #-}!Double,
  lineCap   :: {-# UNPACK #-}!Int,
  color     :: {-# UNPACK #-}!Color,
  clip      :: Path,
  ...
}

By unpacking the constructors, you improve the density of your data, going from a heap structure like this:

Updating a Big State Fast in Haskell

to the denser, stricter:

Updating a Big State Fast in Haskell

Now all the atomic types are laid out in consecutive memory slots. Updating this type will be much faster! BTW, 461.. is the Word representation of the pi field, a bug in my viewer library

You'll also reduce the chance of space leaks.

The cost of passing such a structure around will be very cheap, as the components will be stored in registers.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜