缓冲区的作用

经常听到缓冲区的概念,但始终对它的作用与具体实现有很多疑问,最近拜读了《Linux程序设计》 4TH之后,总算有了大概的理解。
该书举了一个例子,对一个10M文件的复制操作三种不同的实现,速度有不同的体现。
首先包含头文件:

#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>

首先看第一个实现,直接使用read与write的系统底层调用进行文件复制:

int Func1()
{
    char c;
    int in,out;
    in = open("file.in",O_RDONLY);
    out = open("file.out",O_WRONLY|O_CREAT,S_IRUSR|S_IWUSR);
    while(read(in,&c,1)==1)
    {
        write(out,&c,1);
    }
}

以上程序运行之后会将file.in文件复制到file.out文件当中,我们使用TIMEFORMAT="" time ./Test

1.19user 39.37system 0:41.63elapsed 97%CPU (0avgtext+0avgdata 1172maxresident)k
0inputs+24984outputs (0major+63minor)pagefaults 0swaps

也就是我们花了39.37秒才完成了文件复制操作。
我们每次读入1个字节调用一次read函数,10M=1024KB=1048576byte,也就是我们调用了10万次read函数。

我们看下第二个实现,这个实现采用了一个1K的缓冲区:

int Func2()
{
    char buffer[1024];
    int in,out;
    int nread;
    in = open("file.in",O_RDONLY);
    out = open("file.out",O_WRONLY|O_CREAT,S_IRUSR|S_IWUSR);
    while((nread = read(in,&buffer,1024))>0)
    {
        write(out,buffer,nread);
    }
}

0.00user 0.07system 0:00.08elapsed 89%CPU (0avgtext+0avgdata 1112maxresident)k
0inputs+24976outputs (0major+62minor)pagefaults 0swaps

第二个实现则仅仅需要1秒不到的时间即可实现文件复制功能,因为只需要执行1024次操作即可。

下面第三个实现则是使用的标准库里面的文件操作:

int Func3()
{
    FILE* in,*out;
    int c;

    in = fopen("file.in","r");
    out = fopen("file.out","w");

    while((c=fgetc(in) != EOF)
    {
        fputc(c,out);
    }
}

0.40user 0.07system 0:00.52elapsed 92%CPU (0avgtext+0avgdata 1380maxresident)k
0inputs+24976outputs (0major+70minor)pagefaults 0swaps

这种实现所消耗的时间与第二种实现相差不多,但为什么同样是读取一个字符却跟第一个差距这么大,因为在stdio库当中的FILE结构当中实现了一个缓冲区,只有当缓冲区满的时候才会进行调用。

根据上面三个实现,体现了不同的实现效率与性能的极大差异,所以在实际的程序开发当中应该找到性能与稳定的平衡点。