-
Notifications
You must be signed in to change notification settings - Fork 18
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Introduce a circular buffer that always returns continuous chunks #105
Introduce a circular buffer that always returns continuous chunks #105
Conversation
3674675
to
a7e0909
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good.
In terms of implementation, the main comment is probably that I don't understand the Head method.
I'm also having some trouble understanding the tests, as they lack names and or comments to clarify what they are actually testing. With the lack of explicit assertions, it is also somewhat difficult to discern test setup from test execution.
bb42867
to
bf52226
Compare
Just to be safe. I don't think the second shunk will get GCed either way as we hold the baseAddr already. But still.
Co-authored-by: Gustav <[email protected]>
And inline the definition in the mirrored buffer's constructor instead.
This makes it such that we can instantiate MirroredBuffers concurrently.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice! I left some style comments, but they are not important.
note: the same syscalls that make the mirrored buffer possible also make aeron possible. There's no other magic involved. Both aeron and sonic strive to use
/dev/shm
.note: work in progress. I'm planning to testdrive this on edx in 1 weekish on a machine with plenty of RAM to spare
Why do we need this?
To avoid memory allocations and copies in tcp codecs, such as websocket, http or any other exchange specific protocol. What we mean by tcp codec is best understood through an example.
Say a computer wants to communicate with us reliably. This computer sends us bytes through TCP. Now TCP only deals with reading/writing bytes, so we need to agree on a protocol with the computer to interpret those bytes. Moreover, TCP is a stream transport. A single tcp
read
might return 1 or 1000 bytes. We don't know ahead of time. This is in contrast with packet based transports such as UDP, where each network read will return us a single packet. A packet is at most 64KB, so we know ahead of time how much memory to allocate to accommodate any packet.The TCP protocol is simple:
|header|variable payload|
Given the above, a single
read
from the network could give us the following bytes:In the above example we have 3 complete messages of lengths 2 4 and 8 and an incomplete message of length 7 (we only read the first 2 bytes of that message). A further tcp
read
call will probably read the leftover 5 bytes of the 4th message as well as read some more (possibly incomplete) messages.Now we look at how to interpret these messages. In the above example, we can:
read
from the network again.read
syscall expects a slice of bytes. Say we initially allocated a slice of 16 bytes. Until now we used 2+4+8+2(incomplete message) = 16. That means we don't have any space leftover in the current buffer. We can:But we don't want to allocate. That's expensive and unpredictable. We also don't want to copy. That's again expensive, although a bit more predictable. What if, we could use a circular buffer instead?
Now, we can't use a normal circular buffer because each network call expects a contiguous slice of bytes. A circular buffer might wrap, hence returning us two slices to read into, which is incompatible with the
read
/write
syscalls. We also can't use a bip_buffer as TCP is stream, not packet-based.Given the above, we introduce a
mirrored_buffer
: a circular buffer that can always return a contiguous slice of bytes. This fully avoids memory allocations and copies for TCP based codecs.Benchmarks
See
BenchmarkMirroredBuffer
.Docs:
Appendix
Besides allocating and copying, we can go a 3rd, extremely inefficient and mostly unpredictable route: invoke the
read
syscall for each header + message. For the above example, this results in 8 syscalls:read
the 4 bytes, parse it into an integern
and thenread
the payload ofn
bytes.Syscalls are expensive and should be minimized if not totally avoided for latency critical software, such as trading systems. That's why stuff like io_uring or direct-memory-access into network cards exists.
DYI
Output:
sudo pmap -x
:If we just do the first mapping (
MAP_ANONYMOUS | MAP_PRIVATE
withPROT_NONE
) then:So we can see the that two
fd
mappings of theshm
handle replaces the single anonymous one.