The TCP stack is notified every time userland reads from the TCP socket
authorMatthew Dillon <dillon@dragonflybsd.org>
Tue, 3 Aug 2004 00:25:54 +0000 (00:25 +0000)
committerMatthew Dillon <dillon@dragonflybsd.org>
Tue, 3 Aug 2004 00:25:54 +0000 (00:25 +0000)
buffer, because a pure-window-update ACK might be required.  The previous
code would send a pure window update once the difference between the last
advertised window and the current window exceeded 2 segments and also when it
exceeded 1/2 the high water mark.

On GigE networks this can cause a pure window update to occur once every
8 packets (resulting in 5 acks instead of 4) prior to the TCP t_outputq
commit and once every 8 packets (resulting in 2 acks instead of 1) after
the TCP t_outputq commit.  So on a GigE network servicing a high performance
TCP stream, the result is double the number of acks that are actually
necessary.

Conditionalize the 2 segment test with a sysctl.  The sysctl is
net.inet.tcp.avoid_pure_win_update and is set to 1 by default, meaning
that the 2 segment ack does NOT occur (i.e. we should get higher
performance).  It may be turned off to revert to the prior two-ack
algorithm.

This code is considered experimental but since it involves only a pure
window update and we still have the catch-all case this commit is enabling
the new algorithm.  Further tuning might be required, possibly reducing the
1/2 hiwat test to 1/3 or 1/4 hiwat.

Ideally we want to avoid pure window update acks entirely in the normal
streaming case, which means we want to have a TCP buffer that is large
enough to pipeline the networked stream AND the process consuming the data.
On a GigE network this typically equates to the packet batching that occurs
per interrupt.  This batching is typically 8 1448 byte segments == ~11K,
plus turn around overhead and the 1/2 hiwat test so we want an incoming
socket buffer of at least ~11K * 2 + slop which should come to around ~32K
in order to avoid sending (unnecessary) pure window updates on a high
performance TCP stream.

sys/netinet/tcp_output.c

index 622424e..ffa63f6 100644 (file)
@@ -82,7 +82,7 @@
  *
  *     @(#)tcp_output.c        8.4 (Berkeley) 5/24/95
  * $FreeBSD: src/sys/netinet/tcp_output.c,v 1.39.2.20 2003/01/29 22:45:36 hsu Exp $
- * $DragonFly: src/sys/netinet/tcp_output.c,v 1.16 2004/07/17 20:31:31 hsu Exp $
+ * $DragonFly: src/sys/netinet/tcp_output.c,v 1.17 2004/08/03 00:25:54 dillon Exp $
  */
 
 #include "opt_inet6.h"
@@ -148,6 +148,10 @@ int ss_fltsz_local = 4;
 SYSCTL_INT(_net_inet_tcp, OID_AUTO, local_slowstart_flightsize, CTLFLAG_RW,
        &ss_fltsz_local, 1, "Slow start flight size for local networks");
 
+static int avoid_pure_win_update = 1;
+SYSCTL_INT(_net_inet_tcp, OID_AUTO, avoid_pure_win_update, CTLFLAG_RW,
+       &avoid_pure_win_update, 1, "Avoid pure window updates when possible");
+
 /*
  * Tcp output routine: figure out what should be sent and send it.
  */
@@ -379,8 +383,25 @@ again:
                long adv = min(recvwin, (long)TCP_MAXWIN << tp->rcv_scale) -
                        (tp->rcv_adv - tp->rcv_nxt);
 
-               if (adv >= (long) (2 * tp->t_maxseg))
-                       goto send;
+               /*
+                * This ack case typically occurs when the user has drained
+                * the TCP socket buffer sufficiently to warrent an ack
+                * containing a 'pure window update'... that is, an ack that
+                * ONLY updates the tcp window.
+                *
+                * It is unclear why we would need to do a pure window update
+                * past 2 segments if we are going to do one at 1/2 the high
+                * water mark anyway, especially since under normal conditions
+                * the user program will drain the socket buffer quickly. 
+                * The 2-segment pure window update will often add a large
+                * number of extra, unnecessary acks to the stream.
+                *
+                * avoid_pure_win_update now defaults to 1.
+                */
+               if (avoid_pure_win_update == 0) {
+                       if (adv >= (long) (2 * tp->t_maxseg))
+                               goto send;
+               }
                if (2 * adv >= (long) so->so_rcv.sb_hiwat)
                        goto send;
        }