perf(network): Reduce packet allocations and improve loop logic#2659
perf(network): Reduce packet allocations and improve loop logic#2659Caball009 wants to merge 6 commits into
Conversation
7ebd8f1 to
a2267a1
Compare
|
I think it would be nice if we can change the signature of NetPacket from |
Perhaps I can just put the relevant logic from |
I think it's best to do this in a separate pull request because it involves another set of changes. |
3fe1916 to
d4f75e9
Compare
|
| Filename | Overview |
|---|---|
| Core/GameEngine/Include/GameNetwork/NetPacket.h | Constructor signature changed from pointer to const-ref; new CopyTransportMessage() method declared — clean, non-breaking API improvement. |
| Core/GameEngine/Source/GameNetwork/NetPacket.cpp | CopyTransportMessage() extracted from constructor; init() still called first in the constructor before CopyTransportMessage, so field initialization order is preserved. |
| Core/GameEngine/Source/GameNetwork/Connection.cpp | Two per-call NetPacket allocations replaced with function-scoped statics. Reuse of tempref for the oversized-command path is safe because addCommand does not take ownership or modify the ref on failure. |
| Core/GameEngine/Source/GameNetwork/ConnectionManager.cpp | Static NetPacket replaces per-iteration allocation in doRelay; break on empty inBuffer slot is safe given the dense-packing invariant maintained by Transport::doRecv. The spurious deleteInstance(nullptr) at the end of the original doRelay is correctly removed. |
| Core/GameEngine/Source/GameNetwork/Transport.cpp | bufferIndex persists across Read() iterations within a single doRecv() call, avoiding redundant scans from index 0 for each packet. Dense-packing invariant is maintained: bufferIndex resets to 0 each doRecv() invocation, and consumers always clear from slot 0 upward. |
| Core/GameEngine/Source/GameNetwork/LANAPI.cpp | Loop modernized to use size_t and ARRAY_SIZE; break on empty slot added while the !LANbuttonPushed guard is correctly preserved in the loop condition. |
| Core/GameEngine/Source/GameNetwork/NAT.cpp | Loop modernized to use size_t and ARRAY_SIZE; break on empty slot correctly mirrors the dense-packing assumption applied in the other consumers. |
Sequence Diagram
%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
participant Caller
participant Connection
participant Transport
participant ConnectionManager
participant NetPacket
Note over Connection: Static NetPacket allocated once
Caller->>Connection: sendNetCommandMsg(msg, relay)
Connection->>NetPacket: reset()
Connection->>NetPacket: addCommand(tempref)
alt fits in one packet
NetPacket-->>Connection: true
Connection->>Connection: addMessage to m_netCommandList
else too large to fit
NetPacket-->>Connection: false
Connection->>NetPacket: ConstructBigCommandPacketList(tempref)
Connection->>Transport: queueSend per split packet
end
Note over Connection: Static NetPacket reused each loop
Caller->>Connection: doSend()
loop while commands remain
Connection->>NetPacket: reset() then addCommand inner loop
Connection->>Transport: queueSend(data)
end
Transport->>Transport: doRecv - fills m_inBuffer densely from index 0
Note over Transport: bufferIndex persists within each doRecv call
Caller->>ConnectionManager: doRelay()
Note over ConnectionManager: Static NetPacket allocated once
loop "i=0 while m_inBuffer entry length > 0"
ConnectionManager->>NetPacket: reset() then CopyTransportMessage()
ConnectionManager->>ConnectionManager: processNetCommand or sendRemoteCommand
ConnectionManager->>Transport: "clear m_inBuffer entry length = 0"
end
Note over ConnectionManager: break at first empty slot
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
participant Caller
participant Connection
participant Transport
participant ConnectionManager
participant NetPacket
Note over Connection: Static NetPacket allocated once
Caller->>Connection: sendNetCommandMsg(msg, relay)
Connection->>NetPacket: reset()
Connection->>NetPacket: addCommand(tempref)
alt fits in one packet
NetPacket-->>Connection: true
Connection->>Connection: addMessage to m_netCommandList
else too large to fit
NetPacket-->>Connection: false
Connection->>NetPacket: ConstructBigCommandPacketList(tempref)
Connection->>Transport: queueSend per split packet
end
Note over Connection: Static NetPacket reused each loop
Caller->>Connection: doSend()
loop while commands remain
Connection->>NetPacket: reset() then addCommand inner loop
Connection->>Transport: queueSend(data)
end
Transport->>Transport: doRecv - fills m_inBuffer densely from index 0
Note over Transport: bufferIndex persists within each doRecv call
Caller->>ConnectionManager: doRelay()
Note over ConnectionManager: Static NetPacket allocated once
loop "i=0 while m_inBuffer entry length > 0"
ConnectionManager->>NetPacket: reset() then CopyTransportMessage()
ConnectionManager->>ConnectionManager: processNetCommand or sendRemoteCommand
ConnectionManager->>Transport: "clear m_inBuffer entry length = 0"
end
Note over ConnectionManager: break at first empty slot
Reviews (2): Last reviewed commit: "Addressed feedback." | Re-trigger Greptile
| */ | ||
| void ConnectionManager::doRelay() { | ||
| // this is done so we don't have to allocate and delete a packet every time we relay a message. | ||
| static NetPacket* packet = newInstance(NetPacket); |
There was a problem hiding this comment.
It would be better if this was a stack object then
|
|
||
| void init(); | ||
| void reset(); | ||
| void CopyTransportMessage(const TransportMessage& msg); |
| CopyTransportMessage(msg); | ||
| } | ||
|
|
||
| void NetPacket::CopyTransportMessage(const TransportMessage& msg) |
| m_addr = msg->addr; | ||
| m_port = msg->port; | ||
| m_addr = msg.addr; | ||
| m_port = msg.port; |
There was a problem hiding this comment.
This is new. Did not do that before. Is that ok?
| m_transport->m_inBuffer[i].length = 0; | ||
| } | ||
| } else { | ||
| break; |
There was a problem hiding this comment.
Alternatively could do early breaks in the loops. But its ok either way.
| } | ||
| else | ||
| { | ||
| break; |
There was a problem hiding this comment.
So there cannot be non-zero buffer after a zero buffer, yes?
| for (Int i = 0; i < MAX_MESSAGES; ++i) { | ||
| if (m_transport->m_inBuffer[i].length != 0) { | ||
| for (size_t i = 0; i < ARRAY_SIZE(m_transport->m_inBuffer); ++i) { | ||
| if (m_transport->m_inBuffer[i].length > 0) { |
There was a problem hiding this comment.
Status quo: Compares with > 0, != 0
Expectation:
> 0, <= 0
or
== 0, != 0
I can put the loop logic in another PR and put #2866 in here to replace all memory pooled uses of |
This PR makes modest performance improvements in the networking code by reducing the number of dynamic
NetPacketallocations and breaking out loops as early as possible.See commits for cleaner diffs.
TODO:
NetPacketmemory pooled. -> This can be dealt with in a separate PR: refactor(network): Remove memory pool for NetPacket #2866