QNativeSocketEngine: reduce memory usage in nativePendingDatagramSize()

The Windows implementation had the right idea: by using a chunked read,
we can tell the OS to reuse the same buffer over and over, so we don't
need to grow a buffer to the size of the datagram when peeking. This
commit implements that strategy on Unix and changes both implementations
to start at 1500 bytes instead of 8192 (1500 is more than enough for
almost all datagrams we're going to receive).

Let's also not use a static buffer, but a stack-based one. No need to
dedicate 1500 (or, worse, 8192) bytes for something that is only seldom
called.

Change-Id: I320d9d2f42284a69a4cbfffd14dd92a6775bf28b
Reviewed-by: Edward Welbourne <edward.welbourne@qt.io>
This commit is contained in:
Thiago Macieira 2017-08-23 13:11:34 -07:00
parent 3b61cd6ad7
commit a9ff368ac7
2 changed files with 29 additions and 9 deletions

View File

@ -868,22 +868,42 @@ qint64 QNativeSocketEnginePrivate::nativePendingDatagramSize() const
if (recvResult != -1) if (recvResult != -1)
recvResult = value; recvResult = value;
#else #else
QVarLengthArray<char, 8192> udpMessagePeekBuffer(8192); // We need to grow the buffer to fit the entire datagram.
// We start at 1500 bytes (the MTU for Ethernet V2), which should catch
// almost all uses (effective MTU for UDP under IPv4 is 1468), except
// for localhost datagrams and those reassembled by the IP layer.
char udpMessagePeekBuffer[1500];
struct msghdr msg;
struct iovec vec;
memset(&msg, 0, sizeof(msg));
msg.msg_iov = &vec;
msg.msg_iovlen = 1;
vec.iov_base = udpMessagePeekBuffer;
vec.iov_len = sizeof(udpMessagePeekBuffer);
for (;;) { for (;;) {
// the data written to udpMessagePeekBuffer is discarded, so // the data written to udpMessagePeekBuffer is discarded, so
// this function is still reentrant although it might not look // this function is still reentrant although it might not look
// so. // so.
recvResult = ::recv(socketDescriptor, udpMessagePeekBuffer.data(), recvResult = ::recvmsg(socketDescriptor, &msg, MSG_PEEK);
udpMessagePeekBuffer.size(), MSG_PEEK);
if (recvResult == -1 && errno == EINTR) if (recvResult == -1 && errno == EINTR)
continue; continue;
if (recvResult != (ssize_t) udpMessagePeekBuffer.size()) // was the result truncated?
if ((msg.msg_flags & MSG_TRUNC) == 0)
break; break;
udpMessagePeekBuffer.resize(udpMessagePeekBuffer.size() * 2); // grow by 16 times
msg.msg_iovlen *= 16;
if (msg.msg_iov != &vec)
delete[] msg.msg_iov;
msg.msg_iov = new struct iovec[msg.msg_iovlen];
std::fill_n(msg.msg_iov, msg.msg_iovlen, vec);
} }
if (msg.msg_iov != &vec)
delete[] msg.msg_iov;
#endif #endif
#if defined (QNATIVESOCKETENGINE_DEBUG) #if defined (QNATIVESOCKETENGINE_DEBUG)

View File

@ -1146,10 +1146,10 @@ qint64 QNativeSocketEnginePrivate::nativePendingDatagramSize() const
DWORD bufferCount = 5; DWORD bufferCount = 5;
WSABUF * buf = 0; WSABUF * buf = 0;
for (;;) { for (;;) {
// the data written to udpMessagePeekBuffer is discarded, so // We start at 1500 bytes (the MTU for Ethernet V2), which should catch
// this function is still reentrant although it might not look // almost all uses (effective MTU for UDP under IPv4 is 1468), except
// so. // for localhost datagrams and those reassembled by the IP layer.
static char udpMessagePeekBuffer[8192]; char udpMessagePeekBuffer[1500];
buf = new WSABUF[bufferCount]; buf = new WSABUF[bufferCount];
for (DWORD i=0; i<bufferCount; i++) { for (DWORD i=0; i<bufferCount; i++) {