通过几个C代码来检测一下文件系统相关系统调用时间,以及和标准I/O库函数的性能差异。主要以read和fread函数为例。
1.1.1 read系统调用开销
我们来模拟一下read系统调用的开销情况,
代码如下:
#include <unistd.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
int main()
{
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);
exit(0);
}
创建输入文件file.in如下:
dd if=/dev/zero of=file.in bs=1 count=1024000
执行测试如下:
# time ./a.out
real 0m1.080s
user 0m0.064s
sys 0m1.012s
发现主要时间是花在了sys上的,即内核态,当然我们知道是系统调用了,因为就是我们写的嘛。
可以使用strace来跟踪程序,就会不断显示系统调用write和read了。
通过测试时间,其实我们计算得到一次write+read的时间的。就是1.012s/1024000=0.98us。
修改程序如下:
#include <unistd.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
int main()
{
char c;
int in,out;
in = open("file.in",O_RDONLY);
while(read(in,&c,1)==1)
;
exit(0);
}
程序中,去掉了write只留下了read系统调用,运行后时间如下:
# time ./a.out
real 0m0.222s
user 0m0.012s
sys 0m0.208s
可以计算得到每次read的大概时间为0.208/1024000=0.2us.在不同的机器上运行会有不同的结果,大家可以自行测试,祝玩的愉快。
后续会将该程序进行优化。
1.1.2 read系统调用开销二
将代码进行优化如下:
#include <unistd.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
int
main ()
{
char block[1024];
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, block, sizeof(block)) >0 )
write (out, &c, 1);
exit (0);
}
一次read的字节数量修改为1024个字节。
执行后如下,
# time ./a.out
real 0m0.002s
user 0m0.000s
sys 0m0.000s
发现执行时间大幅降低了,性能得到了优化,理论上应该快了千倍被,毕竟系统调用次数减少了千次。
1.1.3 标准I/O库
标准I/O库是由stdio及头文件stdio.h为底层i/o系统调用提供的一个通用接口,例如fopen,fread,fclose等。现在是ANSI标准C的一部分。
1.1.4 性能差异
使用标准I/O库的函数来进行测试,代码如下,
#include <stdio.h>
#include <stdlib.h>
int
main ()
{
int c;
FILE *in,*out;
in = fopen ("file.in", "r");
out = fopen ("file.out", "w");
while((c=fgetc(in))!=EOF)
fputc(c,out);
exit (0);
}
编译执行,
# time ./a.out
real 0m0.036s
user 0m0.032s
sys 0m0.000s
发现sys时间几乎为0,说明主要是在用户态的操作,相比read系统调用所化时间(如下)快了很多。
real 0m1.080s
user 0m0.064s
sys 0m1.012s
这个主要是因为在 stdio 库在 FILE 结构里使用了一个内部缓冲区。只有在缓冲区满时候才进行底层系统调用再刷数据,当然比直接系统调用快很多了。