-
Notifications
You must be signed in to change notification settings - Fork 30k
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
quic: fix memory fragmentation #33912
Conversation
I’ll echo this comment (nodejs/quic#388 (comment)):
i.e. I think this PR should not change the situation at all, and a call to |
I can convert this to use shrink to fit but the change here does appear to fix the originally reported problem: nodejs/quic#388 (comment) ... specifically, I think we can actually get a better result here by avoiding the use of the vector entirely. |
cdd2864
to
bcea9f1
Compare
Let's see how CI goes across all platforms... ;-) https://ci.nodejs.org/job/node-test-commit/39059/ |
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.
lgtm
Normal (non-QUIC) CI: https://ci.nodejs.org/job/node-test-pull-request/31924/ |
bcea9f1
to
de4a6e2
Compare
de4a6e2
to
41b6b11
Compare
Original PR: nodejs/quic#388 Previously, QuicPacket was allocating an std::vector<uint8_t> of NGTCP2_MAX_PKT_SIZE bytes, then the packet would be serialized into the buffer, and the std::vector would be resized based on the number of bytes serialized. I suspect the memory fragmentation that you're seeing is because of those resize operations not freeing memory in chunks that are aligned with the allocation. This changes QuicPacket to use a stack allocation that is always NGTCP2_MAX_PKT_SIZE bytes and the size of the serialized packet is just recorded without any resizing. When the memory is freed now, it should be freed in large enough chunks to cover subsequent allocations. Signed-off-by: James M Snell <[email protected]>
41b6b11
to
cb99d08
Compare
Original PR: nodejs/quic#388 Previously, QuicPacket was allocating an std::vector<uint8_t> of NGTCP2_MAX_PKT_SIZE bytes, then the packet would be serialized into the buffer, and the std::vector would be resized based on the number of bytes serialized. I suspect the memory fragmentation that you're seeing is because of those resize operations not freeing memory in chunks that are aligned with the allocation. This changes QuicPacket to use a stack allocation that is always NGTCP2_MAX_PKT_SIZE bytes and the size of the serialized packet is just recorded without any resizing. When the memory is freed now, it should be freed in large enough chunks to cover subsequent allocations. Signed-off-by: James M Snell <[email protected]> PR-URL: #33912 Reviewed-By: David Carlier <[email protected]> Reviewed-By: Matteo Collina <[email protected]> Reviewed-By: Denys Otrishko <[email protected]>
Landed in d7d79f2 |
@jasnell Okay, do you mind if I open a PR that does shrink memory? Because I don’t think unconditionally allocating the maximum packet length is an acceptable solution, tbh. |
What I've found in testing is either (a) we are sending very small generally fixed sized packets or (b) we are sending very close to full sized packets, there's rarely anything in between and we generally know when we're going to send either. What I think I'd like to see first is: when we know we're going to send a smaller packet size, we just create the The other thing I've been considering is have a ring buffer of QuicPacket instances that we can just reuse. Grab one, serialize into it, send it, put it back on the queue when it's down to be reused so that we can avoid the extra allocations almost entirely. That said, you know I never mind a PR :-) |
To be clear, my concern wasn’t so much the time spent on allocations, but more the fact that these packets now keep memory usage up for a potentially long time. |
|
Ah yeah, if they aren’t kept alive until an ACK from the receiving side I think that should be okay :) |
Yeah, the acks only impact the buffer at the pre-serialized state. The |
Previously, QuicPacket was allocating an std::vector<uint8_t> of NGTCP2_MAX_PKT_SIZE bytes, then the packet would be serialized into the buffer, and the std::vector would be resized based on the number of bytes serialized. I suspect the memory fragmentation that you're seeing is because of those resize operations not freeing memory in chunks that are aligned with the allocation. This changes QuicPacket to use a stack allocation that is always NGTCP2_MAX_PKT_SIZE bytes and the size of the serialized packet is just recorded without any resizing. When the memory is freed now, it should be freed in large enough chunks to cover subsequent allocations.
Original PR: nodejs/quic#388
cc: @nodejs/quic
Checklist
make -j4 test
(UNIX), orvcbuild test
(Windows) passes