kotlinx-io icon indicating copy to clipboard operation
kotlinx-io copied to clipboard

Evaluate and tune Segment::compact behaviour

Open fzhinkin opened this issue 2 years ago • 0 comments
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.

fzhinkin avatar Sep 20 '23 10:09 fzhinkin