6.3 性能相关

在TCP实现中,将用户数据复制到SendQ队列中不仅是因为可能重传数据,这还与性能有关。尤其是SendQ和RecvQ缓冲队列的大小,会对TCP连接的数据吞吐量产生影响。吞吐量是指用户数据字节从发送端发送到接收程序的频率。在要传输大量数据的程序中,我们希望能够最大化这个频率。在没有网络容量或其他限制的情况下,越大的缓冲区通常能够实现越高的吞吐量。

这种情况发生的原因,与底层实现中将数据从缓冲区中存取时的系统耗费有关。如果要传输n字节的数据,使用大小为n的缓冲区调用一次write()方法,通常要比使用大小为1字节的缓冲区调用n次write()方法效率要高很多。[1]然而,如果调用write()方法时使用了比SQS(SendQ队列的大小)大很多的缓冲区,系统还需要将数据从用户地址转换为大小为SQS的块(chunks)。也就是说,套接字底层实现先将SendQ队列缓冲区填满,等待TCP协议将数据转移出去,再重新填满SendQ队列缓冲区,再等待数据转移,反复进行。套接字底层实现每次都要等待数据从SendQ队列中移出,这就以系统耗费的形式(系统需要进行上下文切换)浪费了一些时间。这种系统耗费与重新调用一次write()方法的情况相似。因此,调用write()方法时的实际有效缓冲区大小要受SQS的限制。从InputStream读取数据也是一样的道理:即使提供给read()方法的缓冲区很大,数据还是会被复制成RQS大小的块,在块之间又会产生新的系统耗费。

如果程序的数据吞吐量是一个重要的性能参数,你可能希望通过Socket的setSendBufferSize()和setReceiveBufferSize()方法来改变发送和接收缓冲区的大小。虽然每个缓冲区都有系统指定的最大容量,但是在现代系统上缓冲区的容量通常要比系统的默认大小要大很多。要记住一点,只有当程序要一次发送比缓冲区容量大很多的数据时才需要考虑这些情况。同时还有注意,如果处理了一些从Socket的基本输入流继承而来的更高层次的流(例如,使用它来创建一个FilterOutputStream实例或PrintWriter实例),这些因素产生的效果就会略有不同,因为更高层次的流可能会执行它们字节的内部缓存或增加额外的系统开销。

[1]调用read()方法从Socket的InputStream中读取数据的情况也类似,虽然使用更大的缓冲区并不能保证会返回更多的数据。