kotlinx-io
kotlinx-io copied to clipboard
Evaluate and tune Segment::compact behaviour
trafficstars
When writing one buffer into another, an attempt to compact the tail segment is made to reduce the memory footprint by merging the tail with its predecessor:
// initial state
dstBuffer srcBuffer
[eeeeeeeeeeeeeeFF] [FFFFFFeeeeeeeeee]
// write srcBuffer into dstBuffer
dstBuffer.transferFrom(srcBuffer)
// before compaction:
dstBuffer
[eeeeeeeeeeeeeeFF] [FFFFFFeeeeeeeeee]
// after compaction:
dstBuffer
[FFFFFFFFeeeeeeee]
In the illustration above, the tail segment and its predecessor were merged into a single segment.
However, there are cases when it might be better to avoid compaction for performance reasons:
fun sendMessageWithLengthEncodedBeforePayload(sink: Sink) {
val message = Buffer()
encodePayload(message)
// for formats where a message has a variable length and
// that length should be encoded in a message's header,
// there are not so many options to encode it
// as kotlinx-io doesn't provide an API to override buffer's prefix.
sink.writeInt(message.size)
// here, the message (if its size is below 8088 bytes) will be copied into sink's buffer
// (assuming that the buffer was empty)
sink.transferFrom(message)
// here, however, a merged segment may show a better performance
// depending on how an underlying sink consumes data
sink.flush()
}
There are various factors to consider when deciding whether the compact should be called and I believe that current behavior tends to be a good balance between CPU and memory consumption, but it might be worth reevaluating it.