tcp_send_buffered.c: improve tcp write buffering
* Send data chunk-by-chunk Note: A stream socket doesn't have atomicity requirement. * Increase the chance to use full-sized segments Benchmark numbers in my environment: * Over ESP32 wifi * The peer is NetBSD, which has traditional delayed ack TCP * iperf uses 16384 bytes buffer --- without this patch, CONFIG_IOB_NBUFFERS=36 CONFIG_IOB_BUFSIZE=196 does not work. see https://github.com/apache/incubator-nuttx/pull/2772#discussion_r592820639 --- without this patch, CONFIG_IOB_NBUFFERS=128 CONFIG_IOB_BUFSIZE=196 ``` nsh> iperf -c 192.168.8.1 IP: 192.168.8.103 mode=tcp-client sip=192.168.8.103:5001,dip=192.168.8.1:5001, interval=3, time=30 Interval Bandwidth 0- 3 sec, 4.11 Mbits/sec 3- 6 sec, 4.63 Mbits/sec 6- 9 sec, 4.89 Mbits/sec 9- 12 sec, 4.63 Mbits/sec 12- 15 sec, 4.85 Mbits/sec 15- 18 sec, 4.85 Mbits/sec 18- 21 sec, 5.02 Mbits/sec 21- 24 sec, 3.67 Mbits/sec 24- 27 sec, 4.94 Mbits/sec 27- 30 sec, 4.81 Mbits/sec 0- 30 sec, 4.64 Mbits/sec nsh> ``` --- with this patch, CONFIG_IOB_NBUFFERS=36 CONFIG_IOB_BUFSIZE=196 ``` nsh> iperf -c 192.168.8.1 IP: 192.168.8.103 mode=tcp-client sip=192.168.8.103:5001,dip=192.168.8.1:5001, interval=3, time=30 Interval Bandwidth 0- 3 sec, 5.33 Mbits/sec 3- 6 sec, 5.59 Mbits/sec 6- 9 sec, 5.55 Mbits/sec 9- 12 sec, 5.59 Mbits/sec 12- 15 sec, 5.59 Mbits/sec 15- 18 sec, 5.72 Mbits/sec 18- 21 sec, 5.68 Mbits/sec 21- 24 sec, 5.29 Mbits/sec 24- 27 sec, 4.67 Mbits/sec 27- 30 sec, 4.50 Mbits/sec 0- 30 sec, 5.35 Mbits/sec nsh> ``` --- with this patch, CONFIG_IOB_NBUFFERS=128 CONFIG_IOB_BUFSIZE=196 ``` nsh> iperf -c 192.168.8.1 IP: 192.168.8.103 mode=tcp-client sip=192.168.8.103:5001,dip=192.168.8.1:5001, interval=3, time=30 Interval Bandwidth 0- 3 sec, 5.51 Mbits/sec 3- 6 sec, 4.67 Mbits/sec 6- 9 sec, 4.54 Mbits/sec 9- 12 sec, 5.42 Mbits/sec 12- 15 sec, 5.37 Mbits/sec 15- 18 sec, 5.11 Mbits/sec 18- 21 sec, 5.07 Mbits/sec 21- 24 sec, 5.29 Mbits/sec 24- 27 sec, 5.77 Mbits/sec 27- 30 sec, 4.63 Mbits/sec 0- 30 sec, 5.14 Mbits/sec nsh> ```
This commit is contained in:
parent
e03218ab71
commit
837e1a72a4
@ -67,11 +67,11 @@
|
||||
# define TCP_WBNACK(wrb) ((wrb)->wb_nack)
|
||||
# define TCP_WBIOB(wrb) ((wrb)->wb_iob)
|
||||
# define TCP_WBCOPYOUT(wrb,dest,n) (iob_copyout(dest,(wrb)->wb_iob,(n),0))
|
||||
# define TCP_WBCOPYIN(wrb,src,n) \
|
||||
(iob_copyin((wrb)->wb_iob,src,(n),0,false,\
|
||||
# define TCP_WBCOPYIN(wrb,src,n,off) \
|
||||
(iob_copyin((wrb)->wb_iob,src,(n),(off),false,\
|
||||
IOBUSER_NET_TCP_WRITEBUFFER))
|
||||
# define TCP_WBTRYCOPYIN(wrb,src,n) \
|
||||
(iob_trycopyin((wrb)->wb_iob,src,(n),0,false,\
|
||||
# define TCP_WBTRYCOPYIN(wrb,src,n,off) \
|
||||
(iob_trycopyin((wrb)->wb_iob,src,(n),(off),false,\
|
||||
IOBUSER_NET_TCP_WRITEBUFFER))
|
||||
|
||||
# define TCP_WBTRIM(wrb,n) \
|
||||
|
@ -917,6 +917,44 @@ static inline void send_txnotify(FAR struct socket *psock,
|
||||
#endif /* CONFIG_NET_IPv6 */
|
||||
}
|
||||
|
||||
/****************************************************************************
|
||||
* Name: tcp_max_wrb_size
|
||||
*
|
||||
* Description:
|
||||
* Calculate the desired amount of data for a single
|
||||
* struct tcp_wrbuffer_s.
|
||||
*
|
||||
****************************************************************************/
|
||||
|
||||
static uint32_t tcp_max_wrb_size(FAR struct tcp_conn_s *conn)
|
||||
{
|
||||
const uint32_t mss = conn->mss;
|
||||
uint32_t size;
|
||||
|
||||
/* a few segments should be fine */
|
||||
|
||||
size = 4 * mss;
|
||||
|
||||
/* but it should not hog too many IOB buffers */
|
||||
|
||||
if (size > CONFIG_IOB_NBUFFERS * CONFIG_IOB_BUFSIZE / 2)
|
||||
{
|
||||
size = CONFIG_IOB_NBUFFERS * CONFIG_IOB_BUFSIZE / 2;
|
||||
}
|
||||
|
||||
/* also, we prefer a multiple of mss */
|
||||
|
||||
if (size > mss)
|
||||
{
|
||||
const uint32_t odd = size % mss;
|
||||
size -= odd;
|
||||
}
|
||||
|
||||
DEBUGASSERT(size > 0);
|
||||
ninfo("tcp_max_wrb_size = %" PRIu32 " for conn %p\n", size, conn);
|
||||
return size;
|
||||
}
|
||||
|
||||
/****************************************************************************
|
||||
* Public Functions
|
||||
****************************************************************************/
|
||||
@ -982,6 +1020,7 @@ ssize_t psock_tcp_send(FAR struct socket *psock, FAR const void *buf,
|
||||
{
|
||||
FAR struct tcp_conn_s *conn;
|
||||
FAR struct tcp_wrbuffer_s *wrb;
|
||||
FAR const uint8_t *cp;
|
||||
ssize_t result = 0;
|
||||
bool nonblock;
|
||||
int ret = OK;
|
||||
@ -1043,30 +1082,15 @@ ssize_t psock_tcp_send(FAR struct socket *psock, FAR const void *buf,
|
||||
|
||||
BUF_DUMP("psock_tcp_send", buf, len);
|
||||
|
||||
if (len > 0)
|
||||
cp = buf;
|
||||
while (len > 0)
|
||||
{
|
||||
/* Allocate a write buffer. Careful, the network will be momentarily
|
||||
* unlocked here.
|
||||
*/
|
||||
uint32_t max_wrb_size;
|
||||
unsigned int off;
|
||||
size_t chunk_len = len;
|
||||
ssize_t chunk_result;
|
||||
|
||||
net_lock();
|
||||
if (nonblock)
|
||||
{
|
||||
wrb = tcp_wrbuffer_tryalloc();
|
||||
}
|
||||
else
|
||||
{
|
||||
wrb = tcp_wrbuffer_alloc();
|
||||
}
|
||||
|
||||
if (wrb == NULL)
|
||||
{
|
||||
/* A buffer allocation error occurred */
|
||||
|
||||
nerr("ERROR: Failed to allocate write buffer\n");
|
||||
ret = nonblock ? -EAGAIN : -ENOMEM;
|
||||
goto errout_with_lock;
|
||||
}
|
||||
|
||||
/* Allocate resources to receive a callback */
|
||||
|
||||
@ -1083,7 +1107,7 @@ ssize_t psock_tcp_send(FAR struct socket *psock, FAR const void *buf,
|
||||
|
||||
nerr("ERROR: Failed to allocate callback\n");
|
||||
ret = nonblock ? -EAGAIN : -ENOMEM;
|
||||
goto errout_with_wrb;
|
||||
goto errout_with_lock;
|
||||
}
|
||||
|
||||
/* Set up the callback in the connection */
|
||||
@ -1093,11 +1117,63 @@ ssize_t psock_tcp_send(FAR struct socket *psock, FAR const void *buf,
|
||||
psock->s_sndcb->priv = (FAR void *)psock;
|
||||
psock->s_sndcb->event = psock_send_eventhandler;
|
||||
|
||||
/* Allocate a write buffer. Careful, the network will be momentarily
|
||||
* unlocked here.
|
||||
*/
|
||||
|
||||
/* Try to coalesce into the last wrb.
|
||||
*
|
||||
* But only when it might yield larger segments.
|
||||
* (REVISIT: It might make sense to lift this condition.
|
||||
* IOB boundaries and segment boundaries usually do not match.
|
||||
* It makes sense to save the number of IOBs.)
|
||||
*
|
||||
* Also, for simplicity, do it only when we haven't sent anything
|
||||
* from the the wrb yet.
|
||||
*/
|
||||
|
||||
max_wrb_size = tcp_max_wrb_size(conn);
|
||||
wrb = (FAR struct tcp_wrbuffer_s *)sq_tail(&conn->write_q);
|
||||
if (wrb != NULL && TCP_WBSENT(wrb) == 0 && TCP_WBNRTX(wrb) == 0 &&
|
||||
TCP_WBPKTLEN(wrb) < max_wrb_size &&
|
||||
(TCP_WBPKTLEN(wrb) % conn->mss) != 0)
|
||||
{
|
||||
wrb = (FAR struct tcp_wrbuffer_s *)sq_remlast(&conn->write_q);
|
||||
ninfo("coalesce %zu bytes to wrb %p (%" PRIu16 ")\n", len, wrb,
|
||||
TCP_WBPKTLEN(wrb));
|
||||
DEBUGASSERT(TCP_WBPKTLEN(wrb) > 0);
|
||||
}
|
||||
else if (nonblock)
|
||||
{
|
||||
wrb = tcp_wrbuffer_tryalloc();
|
||||
ninfo("new wrb %p (non blocking)\n", wrb);
|
||||
}
|
||||
else
|
||||
{
|
||||
wrb = tcp_wrbuffer_alloc();
|
||||
ninfo("new wrb %p\n", wrb);
|
||||
}
|
||||
|
||||
if (wrb == NULL)
|
||||
{
|
||||
/* A buffer allocation error occurred */
|
||||
|
||||
nerr("ERROR: Failed to allocate write buffer\n");
|
||||
ret = nonblock ? -EAGAIN : -ENOMEM;
|
||||
goto errout_with_lock;
|
||||
}
|
||||
|
||||
/* Initialize the write buffer */
|
||||
|
||||
TCP_WBSEQNO(wrb) = (unsigned)-1;
|
||||
TCP_WBNRTX(wrb) = 0;
|
||||
|
||||
off = TCP_WBPKTLEN(wrb);
|
||||
if (off + chunk_len > max_wrb_size)
|
||||
{
|
||||
chunk_len = max_wrb_size - off;
|
||||
}
|
||||
|
||||
/* Copy the user data into the write buffer. We cannot wait for
|
||||
* buffer space if the socket was opened non-blocking.
|
||||
*/
|
||||
@ -1112,13 +1188,21 @@ ssize_t psock_tcp_send(FAR struct socket *psock, FAR const void *buf,
|
||||
* remaining data.
|
||||
*/
|
||||
|
||||
result = TCP_WBTRYCOPYIN(wrb, (FAR uint8_t *)buf, len);
|
||||
if (result == -ENOMEM)
|
||||
chunk_result = TCP_WBTRYCOPYIN(wrb, cp, chunk_len, off);
|
||||
if (chunk_result == -ENOMEM)
|
||||
{
|
||||
if (TCP_WBPKTLEN(wrb) > 0)
|
||||
{
|
||||
ninfo("INFO: Allocated part of the requested data\n");
|
||||
result = TCP_WBPKTLEN(wrb);
|
||||
DEBUGASSERT(TCP_WBPKTLEN(wrb) >= off);
|
||||
chunk_result = TCP_WBPKTLEN(wrb) - off;
|
||||
|
||||
/* Note: chunk_result here can be 0 if we are trying
|
||||
* to coalesce into the existing buffer and we failed
|
||||
* to add anything.
|
||||
*/
|
||||
|
||||
DEBUGASSERT(chunk_result >= 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -1129,7 +1213,7 @@ ssize_t psock_tcp_send(FAR struct socket *psock, FAR const void *buf,
|
||||
}
|
||||
else
|
||||
{
|
||||
result = len;
|
||||
DEBUGASSERT(chunk_result == chunk_len);
|
||||
}
|
||||
}
|
||||
else
|
||||
@ -1143,7 +1227,9 @@ ssize_t psock_tcp_send(FAR struct socket *psock, FAR const void *buf,
|
||||
*/
|
||||
|
||||
blresult = net_breaklock(&count);
|
||||
result = TCP_WBCOPYIN(wrb, (FAR uint8_t *)buf, len);
|
||||
ninfo("starting copyin to wrb %p\n", wrb);
|
||||
chunk_result = TCP_WBCOPYIN(wrb, cp, chunk_len, off);
|
||||
ninfo("finished copyin to wrb %p\n", wrb);
|
||||
if (blresult >= 0)
|
||||
{
|
||||
net_restorelock(count);
|
||||
@ -1167,6 +1253,28 @@ ssize_t psock_tcp_send(FAR struct socket *psock, FAR const void *buf,
|
||||
|
||||
send_txnotify(psock, conn);
|
||||
net_unlock();
|
||||
|
||||
if (chunk_result == 0)
|
||||
{
|
||||
DEBUGASSERT(nonblock);
|
||||
break;
|
||||
}
|
||||
|
||||
if (chunk_result < 0)
|
||||
{
|
||||
if (result == 0)
|
||||
{
|
||||
result = chunk_result;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
DEBUGASSERT(chunk_result <= len);
|
||||
DEBUGASSERT(result >= 0);
|
||||
cp += chunk_result;
|
||||
len -= chunk_result;
|
||||
result += chunk_result;
|
||||
}
|
||||
|
||||
/* Check for errors. Errors are signaled by negative errno values
|
||||
@ -1195,6 +1303,11 @@ errout_with_lock:
|
||||
net_unlock();
|
||||
|
||||
errout:
|
||||
if (result > 0)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user