[Message Buffer] Avoid redundant copy

xMessageBufferReceive follows the same “queue by copy” design. But since there should only be a single reader for buffers, it seems like it would be more efficient to allow reading directly out of the buffer, rather than making a copy of the data.

Here’s the current API:

uint8_t buf[1000];
// This unnecessarily copies data into buf
size_t len = xMessageBufferReceive(handle, buf, 1000, 0);
doStuff(buf, len);

Here’s one possible API extension with Peek and Advance:

uint8_t *buf;
// This just sets a pointer to the data, and does not consider the data "received".
size_t len = xMessageBufferPeek(handle, &buf, 1000, 0);
doStuff(buf, len);
// This marks the data as "received" and frees space in the buffer
xMessageBufferAdvance(handle);

There’s an issue issue of reading beyond the circular buffer’s wrap-around boundary though, so this suggestion would probably need an entirely new buffer variant (perhaps ContiguousBuffer) that only Sends if there’s enough free contiguous space available and calculates BufferSpacesAvailable a bit differently.

This proposal would also tackle this question:

The one big problem about reading directly out of the internal buffer is that you would then need another API to allow indicting you were done with that buffer so it could be used by the write side. I would suspect it would also not allow getting a second message before the first was done.

The alternative option would be to send buffer pointers and handle the management on the user side.

@miles I would also be interested in API extensions to eliminate the copying.

I think you’re right about a message that gets “split” at the end of the circular buffer. You can’t really peek that kind of message. So as you said the message buffer implementation could be modified to prevent splitting. But that modification costs RAM, so maybe it could be a compile-time option controlled by INCLUDE_xMessageBufferPeek. That might make the implementation a bit messy with conditionals.

One alternative would be to continue to allow splitting but address the issue in the API. The caller of xMessageBufferPeek() might need to provide a buffer just in case the copy operation is required due to splitting.

I believe you would only need to indicate that you are done with reading a particular message, but the buffer would always be available for writing (assuming enough free space). I proposed a xMessageBufferAdvance() call in my original message for this purpose.

This is possible too, but I don’t think it’s a common enough use case to justify the increased complexity.

I wonder if this mixed approach is worth the effort…
I’m curious, what’s the expected benefit of copying in a message on send but use peek‘n pop on receive ?

Remember that on memory protected ports some times the only way to access the memory is to have the privileged RTOS code copy the data from one memory space to another. If that is not a problem then you can post pointers to messages to the buffer rather than copy the messages.

1 Like

Peek’n pop on the receiving end avoids having to make yet another copy of the message contents.

Yes, but that would require a custom buffer on the producing end to manage the actual data, and logic to free each message when the receiving task is done with it. That’s a bit of reinventing a lot of the functionality already built-in to message buffers. I’d ideally like to leverage as much of existing ecosystem that you already created.

My actual use case is a little more involved. I have multiple producers (fine with a mutex) and a single consumer. Being able to copy to a central buffer avoids each producer needing to maintain its own buffer (with free space margin). Aggregating these margins is more memory efficient and reliable.

Also, queues are problematic for this because the packets are of all different sizes, e.g. (10 to 1000 bytes), so there’s a lot of wasted memory when storing the smallest message in a block that’s sized to accommodate the largest element. Wasted cycles too when copying all 1000 bytes of a queue element that only contains a 10-byte packet.