capnproto / capnproto-java

Cap'n Proto in pure Java
Other
397 stars 85 forks source link

Reusing message readers. #117

Closed MankaranSingh closed 2 years ago

MankaranSingh commented 2 years ago

Hi! I have a loop wich recieves buffer from network and deserialize them into capnp reader. I currently use this method:

messageReader = org.capnproto.Serialize.read(bytebuffer);
bytebuffer.rewind();
return messageReader.getRoot(factory);

// .. access different fields now.

I am not sure if this is an efficient way or if it allocates new memory every time. Any advice on this ? On how to keep memory usage constant to avoid GC ?

dwrensha commented 2 years ago

capnproto-java does not attempt to be allcoation-free, so you're going to see some GC costs in any case.

Your code looks efficient to me, if you are reusing bytebuffer in each loop iteration. The method that you're using calls ByteBuffer.slice() to avoid copying bytebuffer: https://github.com/capnproto/capnproto-java/blob/2f7ce185bb72f98f804bf1e75f6d9f72655da4ed/runtime/src/main/java/org/capnproto/Serialize.java#L163-L165

This means that messageReader will refer to the same underlying memory as bytebuffer. You need to be careful, though: if you modify bytebuffer while messageReader is still active, then you will corrupt the data in messageReader.

capnproto-c++ implements a scratchSpace parameter that makes this kind of buffer reuse a bit easier: https://github.com/capnproto/capnproto/blob/master/c%2B%2B/src/capnp/serialize.h#L131

capnproto-java does not yet support scratch space reuse for Serialize.read(), but it's maybe something that could be added in the future.

MankaranSingh commented 2 years ago

Thanks for the info! I tried updating byte buffer and reusing same reader. it worked in some cases but crashed in others as you said.

One more thing,

currently I need to call Serialize.write(arrayOutputStream, msgBuilder); each time I update the message and pass the arrayOutputStream backed bytebuffer forward.

is there some bytebuffer that directly gets updated on changing values of msgBuilder which I can pass over the wire ?

I suppose Serialize.write(arrayOutputStream, msgBuilder) needs to copy everytime to a new buffer.

MankaranSingh commented 2 years ago

ok, I see that we can directly use segments https://github.com/capnproto/capnproto-java/blob/2f7ce185bb72f98f804bf1e75f6d9f72655da4ed/runtime/src/main/java/org/capnproto/MessageReader.java#L30

my only problem is I cannot send multipart messages over the network due to some reasons :/

dwrensha commented 2 years ago

That MessageReader constructor will work even if ByteBuffer[] segmentSlices has length 1. (Though if you use this instead of Serialize then you need to handle message framing/delimiting on your own.)

dwrensha commented 2 years ago

is there some bytebuffer that directly gets updated on changing values of msgBuilder which I can pass over the wire ?

You could write your own implementation of Allocator to support that https://github.com/capnproto/capnproto-java/blob/2f7ce185bb72f98f804bf1e75f6d9f72655da4ed/runtime/src/main/java/org/capnproto/Allocator.java#L6-L13

https://github.com/capnproto/capnproto-java/blob/2f7ce185bb72f98f804bf1e75f6d9f72655da4ed/runtime/src/main/java/org/capnproto/MessageBuilder.java#L43-L48

MankaranSingh commented 2 years ago

Thanks for all the tips ! Looks like everything I wanted to achieve was already there in the API. Since the messages I use are of fixed size, I computed their size in advanced and then used the MessageBuilder(ByteBuffer firstSegmentSlice); constructor to provide my own byte buffer of enough length so that list of SegmentSlices only contains one buffer. I share this buffer over the network so the serialize step / extra copy is totally removed now !

Thanks for the help !