RFC: unsafeShrinkMutableByteArray#

Hello Simon (et al.) While experimenting with refactoring/improving integer-gmp, I'd like to represent a GMP number just by a ByteArrays# (and thus save a redundant limb-count field). However, for that I'd need an efficient way to resize a MutableByteArray# for the result value in case its initial size over- (or under-)allocated. Right now I'd re-allocate via newByteArray# with the final size and copyMutableByteArray#, and now I was wondering if we couldn't simply have an unsafeShrinkMutableByteArray# :: MutableByteArray# s# -> Int# -> State# s -> State# s operation, which would allow for zero-copying. (the 'unsafe' denotes this wouldn't check if the new size is less-or-equal to the current size, and that one has to be careful when subsequently using sizeofMutableByteArray# which is currently a pure function) Is such an operation feasible, or is there something in the RTS/GC that would trip over when a ByteArray has suddenly a smaller byte-count than its originally newByteArray#'ed amount? PS: maybe unsafeShrinkMutableByteArray# could unsafe-freeze the ByteArray# while at it (thus be called something like unsafeShrinkAndFreezeMutableByteArray#), as once I know the final smaller size I would freeze it anyway right after shrinking. Cheers, hvr

Yes, this will cause problems in some modes, namely -debug and -prof that need to be able to scan the heap linearly. Usually we invoke the OVERWRITING_CLOSURE() macro which overwrites the original closure with zero words, but this won't work in your case because you want to keep the original contents. So you'll need a version of OVERWRITING_CLOSURE() that takes the size that you want to retain, and doesn't overwrite that part of the closure. This is probably a good idea anyway, because it might save some work in other places where we use OVERWRITING_CLOSURE(). I am worried about sizeofMutableByteArray# though. It wouldn't be safe to call sizeofMutableByteArray# on the original array, just in case it was evaluated after the shrink. You could make things slightly safer by having unsafeShrinkMutableByteArray# return the new array, so that you have a safe way to call sizeofMutableByteArray# after the shrink. This still doesn't seem very satisfactory to me though. Cheers, Simon On 12/07/2014 00:51, Herbert Valerio Riedel wrote:
Hello Simon (et al.)
While experimenting with refactoring/improving integer-gmp, I'd like to represent a GMP number just by a ByteArrays# (and thus save a redundant limb-count field). However, for that I'd need an efficient way to resize a MutableByteArray# for the result value in case its initial size over- (or under-)allocated.
Right now I'd re-allocate via newByteArray# with the final size and copyMutableByteArray#, and now I was wondering if we couldn't simply have an
unsafeShrinkMutableByteArray# :: MutableByteArray# s# -> Int# -> State# s -> State# s
operation, which would allow for zero-copying. (the 'unsafe' denotes this wouldn't check if the new size is less-or-equal to the current size, and that one has to be careful when subsequently using sizeofMutableByteArray# which is currently a pure function)
Is such an operation feasible, or is there something in the RTS/GC that would trip over when a ByteArray has suddenly a smaller byte-count than its originally newByteArray#'ed amount?
PS: maybe unsafeShrinkMutableByteArray# could unsafe-freeze the ByteArray# while at it (thus be called something like unsafeShrinkAndFreezeMutableByteArray#), as once I know the final smaller size I would freeze it anyway right after shrinking.
Cheers, hvr

On 2014-07-12 at 17:40:07 +0200, Simon Marlow wrote:
Yes, this will cause problems in some modes, namely -debug and -prof that need to be able to scan the heap linearly.
...and I assume we don't want to fallback to a non-zerocopy mode for -debug & -prof in order avoid distorting the profiling measurements either?
Usually we invoke the OVERWRITING_CLOSURE() macro which overwrites the original closure with zero words, but this won't work in your case because you want to keep the original contents. So you'll need a version of OVERWRITING_CLOSURE() that takes the size that you want to retain, and doesn't overwrite that part of the closure. This is probably a good idea anyway, because it might save some work in other places where we use OVERWRITING_CLOSURE().
I'm not sure I follow. What's the purpose of overwriting the original closure payload with zeros while in debug/profile mode? (and on what occasions that would be problematic for a MutableByteArray does it happen?)
I am worried about sizeofMutableByteArray# though. It wouldn't be safe to call sizeofMutableByteArray# on the original array, just in case it was evaluated after the shrink. You could make things slightly safer by having unsafeShrinkMutableByteArray# return the new array, so that you have a safe way to call sizeofMutableByteArray# after the shrink. This still doesn't seem very satisfactory to me though.
...as a somewhat drastic obvious measure, one could change the type-sig of sizeofMutableByteArray# to :: MutableByteArray# s a -> State# s -> (# State# s, Int# #) and fwiw, I could find only one use-site of sizeofMutableByteArray# inside ghc.git, so I'm wondering if that primitive is used much anyway. btw, is it currently safe to call/evaluate sizeofMutableByteArray# on the original MBA after a unsafeFreezeByteArray# was performed? Otoh, if we are to thread a MutableByteArray# through the call anyway, can't we just combine shrinking and freezing in one primop (as suggested below)? [...]
PS: maybe unsafeShrinkMutableByteArray# could unsafe-freeze the ByteArray# while at it (thus be called something like unsafeShrinkAndFreezeMutableByteArray#), as once I know the final smaller size I would freeze it anyway right after shrinking.

On 13/07/14 14:15, Herbert Valerio Riedel wrote:
On 2014-07-12 at 17:40:07 +0200, Simon Marlow wrote:
Yes, this will cause problems in some modes, namely -debug and -prof that need to be able to scan the heap linearly.
...and I assume we don't want to fallback to a non-zerocopy mode for -debug & -prof in order avoid distorting the profiling measurements either?
I suppose that would be doable. Not ideal, but doable. In profiling mode you could arrange for the extra allocation to be assigned to CCS_OVERHEAD, so that it gets counted as profiling overhead. You'd still have the time overhead of the copy though.
Usually we invoke the OVERWRITING_CLOSURE() macro which overwrites the original closure with zero words, but this won't work in your case because you want to keep the original contents. So you'll need a version of OVERWRITING_CLOSURE() that takes the size that you want to retain, and doesn't overwrite that part of the closure. This is probably a good idea anyway, because it might save some work in other places where we use OVERWRITING_CLOSURE().
I'm not sure I follow. What's the purpose of overwriting the original closure payload with zeros while in debug/profile mode? (and on what occasions that would be problematic for a MutableByteArray does it happen?)
Certain features of the RTS need to be able to scan the contents of the heap by linearly traversing the memory. When there are gaps between heap objects, there needs to be a way to find the start of the next heap object, so currently when we overwrite an object with a smaller one we clear the payload with zeroes. There are more efficient ways, such as overwriting with a special "gap" object, but since the times we need to do this are not performance critical, we haven't optimised it. Currently we need to do this * in debug mode, for heap sanity checking * in profiling mode, for biographical profiling The macro that does this, OVERWRITING_CLOSURE() currently overwrites the whole payload of the closure with zeroes, whereas you want to retain part of the closure, so you would need a different version of this macro.
I am worried about sizeofMutableByteArray# though. It wouldn't be safe to call sizeofMutableByteArray# on the original array, just in case it was evaluated after the shrink. You could make things slightly safer by having unsafeShrinkMutableByteArray# return the new array, so that you have a safe way to call sizeofMutableByteArray# after the shrink. This still doesn't seem very satisfactory to me though.
...as a somewhat drastic obvious measure, one could change the type-sig of sizeofMutableByteArray# to
:: MutableByteArray# s a -> State# s -> (# State# s, Int# #)
and fwiw, I could find only one use-site of sizeofMutableByteArray# inside ghc.git, so I'm wondering if that primitive is used much anyway.
I think that would definitely be better, if it is possible without too much breakage. Once we have operations that change the size of an array, the operation that reads the size should be stateful.
btw, is it currently safe to call/evaluate sizeofMutableByteArray# on the original MBA after a unsafeFreezeByteArray# was performed?
Probably safe, but better to avoid doing it if you can.
Otoh, if we are to thread a MutableByteArray# through the call anyway, can't we just combine shrinking and freezing in one primop (as suggested below)?
I don't think this makes anything easier. You still need to overwrite the unused part of the array, and sizeofMutableByteArray# is still dangerous. Cheers, Simon
[...]
PS: maybe unsafeShrinkMutableByteArray# could unsafe-freeze the ByteArray# while at it (thus be called something like unsafeShrinkAndFreezeMutableByteArray#), as once I know the final smaller size I would freeze it anyway right after shrinking.
participants (2)
-
Herbert Valerio Riedel
-
Simon Marlow