高级I/O(七)--readv和writev函数

From: http://blog.chinaunix.net/uid-26822401-id-3158225.html

readv和write函数让我们在单个函数调用里从多个不连续的缓冲里读入或写出。这些操作被称为分散读(scatter read)和集合写(gather write)。



  1. #include <sys/uio.h>

  2. ssize_t readv(int filedes, const struct iovec *iov, int iovcnt);

  3. ssize_t writev(int filedes, const struct iovec *iov, int iovcnt);

  4. 两者都返回读或写的字节数,错误返回-1。


两个函数的第二个参数都是一个iovec结构体数组的指针:
struct iovec {
  void *iov_base;  /* starting address of buffer */
  size_t iov_len;  /* size of buffer */
};


iov数组里的元素数量由iovcnt指定。它限制于IOV_MAX(第二章)。下图显示了这两个函数的参数和iovec结构体的关系:




writev函数把缓冲的输出数据按顺序集合到一起:iov[0]、iov[1]、到iov[iovcnt-1];writev返回输出字节的总数量,它应该等于所有缓冲长度的和。


readv函数把数据按顺序分散到缓冲里,问题在处理下一个缓冲时填满第一个。readv返回被读的字节总数。如果没有更多数据和碰到文件末尾时返回0的计数。


这两个函数起源于4.2BSD,后来加入到SVR4。这两个函数被SUS的XSI扩展包含。


尽管SUS定义了缓冲地址为一个void *,然而许多未跟上标准的实现仍使用char *代替。


在20.8节里,函数_db_writeidx里,我们需要连续地写两个缓冲到一个文件。第二个要输出的缓冲是一个传给调用者的参数,而第一个缓冲是我们创建的,包含第二个缓冲的长度和文件里其它信息的一个文件偏移量。我们可以有三种方法做这个。


1、调用write两次,一个缓冲一次;


2、分配一个我们自己的缓冲,它足够大来包含两个缓冲,然后把两者拷贝到新缓冲。我们然后为这个新缓冲调用write一次。


3、调用writev来输出两个缓冲。


我们在20.8节使用的解决方案是使用writev,但是把它跟其它两种方案比较是有指导性的。


下表显示了提到的三种方法的结果。 


writev和其它技术的时间结果比较
操作 Linux (Intel x86) Mac OS X (PowerPC)
用户 系统 时钟 用户 系统 时钟
两次write 1.29 3.15 7.39 1.60 17.40 19.84
缓冲拷贝,然后一次write 1.03 1.98 6.47 1.10 11.09 12.54
一次writev 0.70  2.72 6.41 0.86 13.58 14.72


我们测量的测试程序输出一个100字节的头,接着一个200字节的数据。这被完成1048576次,产生一个300M的文件。测试程序有三个分离的条件--上表中每个测量的技术是一个。我们使用times(8.16节)来得到用户CUP时间,系统CPU系统和挂钟时间,在write前后。所有三个时间以秒显示。

正如我们意料的,系统时间当我们调用两次write时增加,对比于一次的write或writev。这和3.9节的测量结果对应。


接着,注意CPU时间的总和(用户加上系统),当我们执行缓冲拷贝接着单个write比单个writev调用要少。对于单个write,我们在用户层拷贝缓冲到一个分段运输的缓冲,然后在我们调用write时内核会把数据拷贝到它内部的缓冲。对于writev,我们应该做更少的拷贝,因为内核只需要直接把数据拷贝到它的分段运输缓冲里。然而,为如此少的数据使用writev的固定花费,比所得要大。当我们需要拷贝的数据量增加时,在我们程序里拷贝缓冲会便耗时,而writev替代地将更具吸引力。


小必不要被上表中Linux相对于Mac的性能影响太多。这两个电脑非常不同:它们有不同的处理器架构、不同的RAM量、不同速度的磁盘。为了公平比较两个操作系统,我们需要使用相同的硬件。


总而言之,我们应该总是尝试使用所需的最少次数的系统调用来完成工作。如果我们写少量数据,那么我们将发现自己拷贝数据和使用单个write而不是使用writev会更不耗时。然而,我们可能发现,性能的好处比不上需要管理我们自己的分段运输缓冲的复杂性代价。


你可能感兴趣的:(高级I/O(七)--readv和writev函数)