Unfortunately, this makes sense to me, though it’s hard to fully explain without seeing the Storable instance being used. After looking at it a bit I can’t see how it would be growing linearly with row traversals unless the concrete type of values has a field that contains it already converted to a ByteString, which of course is just a thunk until the first time it’s used. Because the second call to (getTables values) is referencing all of them, it can’t free them as their rows are passed; it instead has to wait until the end.
Does anyone have a better idea?
In general, I have a longstanding complaint at how lazy evaluation leads to surprising performance characteristics, even for people who’ve used Haskell a long time (or, at least, even for myself). If I had a do-over, I’d make strictness the default. Heh.
Unfortunately, this makes sense to me, though it’s hard to fully explain without seeing the Storable instance being used. After looking at it a bit I can’t see how it would be growing linearly with row traversals unless the concrete type of
valueshas a field that contains it already converted to a ByteString, which of course is just a thunk until the first time it’s used. Because the second call to(getTables values)is referencing all of them, it can’t free them as their rows are passed; it instead has to wait until the end.Does anyone have a better idea?
In general, I have a longstanding complaint at how lazy evaluation leads to surprising performance characteristics, even for people who’ve used Haskell a long time (or, at least, even for myself). If I had a do-over, I’d make strictness the default. Heh.