Skip to content

Commit

Permalink
fix: perf: uncork max message size from 144 KB to as much as we want …
Browse files Browse the repository at this point in the history
…based on receive window size.

#22
skywind3000/kcp#291
  • Loading branch information
vis2k committed Nov 28, 2021
1 parent 957d13c commit 8764652
Show file tree
Hide file tree
Showing 3 changed files with 27 additions and 19 deletions.
14 changes: 7 additions & 7 deletions kcp2k/Assets/Tests/Editor/ClientServerTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -299,7 +299,7 @@ public void ClientToServerReliableMaxSizedMessage()
server.Start(Port);
ConnectClientBlocking();

byte[] message = new byte[KcpConnection.ReliableMaxMessageSize];
byte[] message = new byte[KcpConnection.ReliableMaxMessageSize(ReceiveWindowSize)];
for (int i = 0; i < message.Length; ++i)
message[i] = (byte)(i & 0xFF);
Log.Info($"Sending {message.Length} bytes = {message.Length / 1024} KB message");
Expand Down Expand Up @@ -356,10 +356,10 @@ public void ClientToServerTooLargeReliableMessage()
server.Start(Port);
ConnectClientBlocking();

byte[] message = new byte[KcpConnection.ReliableMaxMessageSize + 1];
byte[] message = new byte[KcpConnection.ReliableMaxMessageSize(ReceiveWindowSize) + 1];

#if UNITY_2018_3_OR_NEWER
UnityEngine.TestTools.LogAssert.Expect(UnityEngine.LogType.Error, $"Failed to send reliable message of size {message.Length} because it's larger than ReliableMaxMessageSize={KcpConnection.ReliableMaxMessageSize}");
UnityEngine.TestTools.LogAssert.Expect(UnityEngine.LogType.Error, $"Failed to send reliable message of size {message.Length} because it's larger than ReliableMaxMessageSize={KcpConnection.ReliableMaxMessageSize(ReceiveWindowSize)}");
#endif
SendClientToServerBlocking(new ArraySegment<byte>(message), KcpChannel.Reliable);
Assert.That(serverReceived.Count, Is.EqualTo(0));
Expand Down Expand Up @@ -437,7 +437,7 @@ public void ClientToServerMultipleReliableMaxSizedMessagesAtOnce()
for (int i = 0; i < 10; ++i)
{
// create message, fill with unique data (j+i & 0xff)
byte[] message = new byte[KcpConnection.ReliableMaxMessageSize];
byte[] message = new byte[KcpConnection.ReliableMaxMessageSize(ReceiveWindowSize)];
for (int j = 0; j < message.Length; ++j)
message[j] = (byte)((j + i) & 0xFF);
messages.Add(message);
Expand Down Expand Up @@ -555,7 +555,7 @@ public void ServerToClientReliableMaxSizedMessage()
ConnectClientBlocking();
int connectionId = ServerFirstConnectionId();

byte[] message = new byte[KcpConnection.ReliableMaxMessageSize];
byte[] message = new byte[KcpConnection.ReliableMaxMessageSize(ReceiveWindowSize)];
for (int i = 0; i < message.Length; ++i)
message[i] = (byte)(i & 0xFF);

Expand Down Expand Up @@ -615,9 +615,9 @@ public void ServerToClientTooLargeReliableMessage()
ConnectClientBlocking();
int connectionId = ServerFirstConnectionId();

byte[] message = new byte[KcpConnection.ReliableMaxMessageSize + 1];
byte[] message = new byte[KcpConnection.ReliableMaxMessageSize(ReceiveWindowSize) + 1];
#if UNITY_2018_3_OR_NEWER
UnityEngine.TestTools.LogAssert.Expect(UnityEngine.LogType.Error, $"Failed to send reliable message of size {message.Length} because it's larger than ReliableMaxMessageSize={KcpConnection.ReliableMaxMessageSize}");
UnityEngine.TestTools.LogAssert.Expect(UnityEngine.LogType.Error, $"Failed to send reliable message of size {message.Length} because it's larger than ReliableMaxMessageSize={KcpConnection.ReliableMaxMessageSize(ReceiveWindowSize)}");
#endif
SendServerToClientBlocking(connectionId, new ArraySegment<byte>(message), KcpChannel.Reliable);
Assert.That(clientReceived.Count, Is.EqualTo(0));
Expand Down
24 changes: 15 additions & 9 deletions kcp2k/Assets/kcp2k/highlevel/KcpConnection.cs
Original file line number Diff line number Diff line change
Expand Up @@ -49,35 +49,36 @@ public abstract class KcpConnection
// kcp does fragmentation, so max message is way larger than MTU.
//
// -> runtime MTU changes are disabled: mss is always MTU_DEF-OVERHEAD
// -> Send() checks if fragment count < WND_RCV, so we use WND_RCV - 1.
// note that Send() checks WND_RCV instead of wnd_rcv which may or
// may not be a bug in original kcp. but since it uses the define, we
// can use that here too.
// -> Send() checks if fragment count < rcv_wnd, so we use rcv_wnd - 1.
// NOTE that original kcp has a bug where WND_RCV default is used
// instead of configured rcv_wnd, limiting max message size to 144 KB
// https://github.com/skywind3000/kcp/pull/291
// we fixed this in kcp2k.
// -> we add 1 byte KcpHeader enum to each message, so -1
//
// IMPORTANT: max message is MTU * WND_RCV, in other words it completely
// IMPORTANT: max message is MTU * rcv_wnd, in other words it completely
// fills the receive window! due to head of line blocking,
// all other messages have to wait while a maxed size message
// is being delivered.
// => in other words, DO NOT use max size all the time like
// for batching.
// => sending UNRELIABLE max message size most of the time is
// best for performance (use that one for batching!)
public const int ReliableMaxMessageSize = (Kcp.MTU_DEF - Kcp.OVERHEAD - CHANNEL_HEADER_SIZE) * (Kcp.WND_RCV - 1) - 1;
public static int ReliableMaxMessageSize(uint rcv_wnd) => (Kcp.MTU_DEF - Kcp.OVERHEAD - CHANNEL_HEADER_SIZE) * ((int)rcv_wnd - 1) - 1;

// unreliable max message size is simply MTU - channel header size
public const int UnreliableMaxMessageSize = Kcp.MTU_DEF - CHANNEL_HEADER_SIZE;

// buffer to receive kcp's processed messages (avoids allocations).
// IMPORTANT: this is for KCP messages. so it needs to be of size:
// 1 byte header + MaxMessageSize content
byte[] kcpMessageBuffer = new byte[1 + ReliableMaxMessageSize];
byte[] kcpMessageBuffer;// = new byte[1 + ReliableMaxMessageSize];

// send buffer for handing user messages to kcp for processing.
// (avoids allocations).
// IMPORTANT: needs to be of size:
// 1 byte header + MaxMessageSize content
byte[] kcpSendBuffer = new byte[1 + ReliableMaxMessageSize];
byte[] kcpSendBuffer;// = new byte[1 + ReliableMaxMessageSize];

// raw send buffer is exactly MTU.
byte[] rawSendBuffer = new byte[Kcp.MTU_DEF];
Expand Down Expand Up @@ -143,6 +144,11 @@ protected void SetupKcp(bool noDelay, uint interval = Kcp.INTERVAL, int fastRese
// message afterwards.
kcp.SetMtu(Kcp.MTU_DEF - CHANNEL_HEADER_SIZE);

// create message buffers AFTER window size is set
// see comments on buffer definition for the "+1" part
kcpMessageBuffer = new byte[1 + ReliableMaxMessageSize(receiveWindowSize)];
kcpSendBuffer = new byte[1 + ReliableMaxMessageSize(receiveWindowSize)];

this.timeout = timeout;
state = KcpState.Connected;

Expand Down Expand Up @@ -551,7 +557,7 @@ void SendReliable(KcpHeader header, ArraySegment<byte> content)
}
}
// otherwise content is larger than MaxMessageSize. let user know!
else Log.Error($"Failed to send reliable message of size {content.Count} because it's larger than ReliableMaxMessageSize={ReliableMaxMessageSize}");
else Log.Error($"Failed to send reliable message of size {content.Count} because it's larger than ReliableMaxMessageSize={ReliableMaxMessageSize(kcp.rcv_wnd)}");
}

void SendUnreliable(ArraySegment<byte> message)
Expand Down
8 changes: 5 additions & 3 deletions kcp2k/Assets/kcp2k/kcp/Kcp.cs
Original file line number Diff line number Diff line change
Expand Up @@ -264,9 +264,11 @@ public int Send(byte[] buffer, int offset, int len)

// original kcp uses WND_RCV const instead of rcv_wnd runtime:
// https://github.com/skywind3000/kcp/pull/291/files
// which always limits max message size to 144 KB.
// this might be a bug.
if (count >= WND_RCV) return -2;
// which always limits max message size to 144 KB:
//if (count >= WND_RCV) return -2;
// using configured rcv_wnd uncorks max message size to 'any':
UnityEngine.Debug.Log($"Send len=" + buffer.Length + " frag count=" + count + " WND_RCV=" + WND_RCV + " rcv_wnd=" + rcv_wnd);
if (count >= rcv_wnd) return -2;

if (count == 0) count = 1;

Expand Down

0 comments on commit 8764652

Please sign in to comment.