标准 IO 库的缓冲区
首先需要明确一点: 标准 IO 库提供缓冲区的目的是尽可能减少系统调用 read 和 write 函数的调用次数. 之后会给出例子直观的显示缓冲区的大小对程序性能的影响.
标准 IO 提供了 3 种类型的缓冲:
- 全缓冲: 其特点是需要填满缓冲区后才进行实际的 IO 操作(当然, 也可以使用
flush
对缓冲区进行冲洗), 一般对于磁盘上的文件实施的是全缓冲. 默认全缓冲的大小为 4096; - 行缓冲: 当输入输出遇到换行符时, 才执行 IO 操作. 当流涉及一个终端时, 通常使用行缓冲. 行缓冲的长度是固定的, 稍微小一些, 默认是 1024;
- 不带缓冲: 标准 IO 库不对字符进行缓冲. 比如标准错误输出 stderr 通常是不带缓冲的, 这样即使没有换行符, 出错信息也能尽快显示. 此时缓冲的大小为 1.
测试缓冲大小对程序性能的影响
下面给出一个例子展示缓冲区的大小对程序性能的影响, 该程序实现对文件的读, 然后将内容显示到屏幕上. 根据上面介绍的内容, 使用标准 IO 库对磁盘上的文件进行处理时, 实施的全缓冲. 之后, 我们将缓冲类型设置为不带缓冲, 从而比较两种类型的缓冲对程序性能的影响.
/* test_buffer.c */
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <sys/time.h>
#define oops(msg) {perror(msg); exit(1);}
int main() {
char buf[100];
FILE *fp;
if ((fp = fopen("file", "r")) == NULL)
oops("fopen");
/*setbuf(fp, NULL);*/
struct timeval t_val, t_val_end, t_result;
gettimeofday(&t_val, NULL);
while (fgets(buf, 100, fp) != NULL)
printf("%s", buf);
gettimeofday(&t_val_end, NULL);
timersub(&t_val_end, &t_val, &t_result);
double consume = t_result.tv_sec + (1.0 * t_result.tv_usec)/1000000;
printf("end.elapsed time= %fs \n", consume);
}
其中文件 file 有 20 行, 总共 238 个字符.
- 当执行上述代码后(注意此时
setbuf(fp, NULL);
被注释了), 结果为:
end.elapsed time= 0.000092s
- 当将
setbuf(fp, NULL);
的注释去掉, 此时fp
对应的流是不带缓冲的, 代码的执行结果为:
end.elapsed time= 0.000265s
结果在我们的意料之中. 此时由于关闭缓冲后, 系统调用频繁, 所消耗的时间无疑会增多.
显示缓冲的类型以及大小
下面的例子展示了各个流的缓冲类型以及相应的大小:
/* buffer_info.c */
#include <stdio.h>
#include <stdlib.h>
void pr_stdio(const char*, FILE*);
int is_unbuffered(FILE*);
int is_linebuffered(FILE*);
int buffer_size(FILE*);
#define oops(msg) {perror(msg); exit(1);}
int main() {
FILE *fp;
pr_stdio("stdin", stdin);
pr_stdio("stdout", stdout);
pr_stdio("stderr", stderr);
if ((fp = fopen("/etc/passwd", "r")) == NULL)
oops("fopen");
if (getc(fp) == EOF)
oops("getc error");
pr_stdio("/etc/passwd", fp);
exit(0);
}
void pr_stdio(const char *name, FILE *fp) {
printf("stream = %s, ", name);
if (is_unbuffered(fp))
printf("unbuffered");
else if (is_linebuffered(fp))
printf("line buffered");
else
printf("fully buffered");
printf (", buffer size = %d\n", buffer_size(fp));
}
int is_unbuffered(FILE *fp) {
return(fp->_flags & _IO_UNBUFFERED);
}
int is_linebuffered(FILE *fp) {
return(fp->_flags & _IO_LINE_BUF);
}
int buffer_size(FILE *fp) {
return(fp->_IO_buf_end - fp->_IO_buf_base);
}
程序运行的结果为:
stream = stdin, line buffered, buffer size = 1024
stream = stdout, line buffered, buffer size = 1024
stream = stderr, unbuffered, buffer size = 1
stream = /etc/passwd, fully buffered, buffer size = 4096