8、标准输入输出的效率
这一节,将使用标准输入输出和直接使用系统调用进行输入输出的时间进行了对比,主要是对: char 的 std I/O , line 的 std I/O ,系统调用设置最优缓存的 I/O ,以及系统调用没有设置缓存的 =I/O = 进行了对比,对比的表格如下:
使用标准输入输出库的时间
+------------------------------------------------------------------------------------------------------------------------------+
| Function | User CPU (seconds) | System CPU (seconds) | Clock time (seconds) | Bytes of program text |
|-----------------------------------+--------------------+----------------------+----------------------+-----------------------|
| best time from Figure 3.5 | 0.01 | 0.18 | 6.67 | |
|-----------------------------------+--------------------+----------------------+----------------------+-----------------------|
| fgets, fputs | 2.59 | 0.19 | 7.15 | 139 |
|-----------------------------------+--------------------+----------------------+----------------------+-----------------------|
| getc, putc | 10.84 | 0.27 | 12.07 | 120 |
|-----------------------------------+--------------------+----------------------+----------------------+-----------------------|
| fgetc, fputc | 10.44 | 0.27 | 11.42 | 120 |
|-----------------------------------+--------------------+----------------------+----------------------+-----------------------|
| single byte time from Figure 3.5 | 124.89 | 161.65 | 288.64 | |
+------------------------------------------------------------------------------------------------------------------------------+
这里,第1行的 best time from Figure3.5 对应原书中的相应图形,其实就是之前第3章8节中的 I/O 效率对比表格中,直接使用系统调用,传入最优缓存大小所用的时间(使得系统调用次数最少从而消耗时间最少)。我们通过操作一个 98.5MB 大小的文件(大约 300 万行),来显示这些数据,通过上表我们发现:
-
直接使用库函数,并不比最优缓存的系统调用
I/O差很多。使用库函数,我们不用考虑系统最优缓存了,有时候只是考虑buffer大小就行了,这比系统调用考虑最优缓存方便多了。如果line的std I/O使用char的std I/O实现的话,其消耗的时间要比char的std I/O要大,但是由于line的std I/O是用高效率的memccopy实现的,所以快。 -
使用
char的std I/O比没有缓存的系统调用I/O要快,尽管循环次数差不多,而且char的还额外增加了一点sys的循环,但是一次系统调用的代价,比一次函数调用的代价大很多,所以,char的std I/O比纯粹没有缓存系统调用的I/O代价要小。 -
使用
std I/O需要增加一些system time用来拷贝。一般重要的程序,I/O应该占用user time更多。
译者注
原文参考
9、二进制 I/O
前面函数都是以字节或者行的方式进行操作。如果对于二进制 I/O ,我们更愿意一次读或写整个结构变量;虽然前面那样也能实现读写整个结构变量,但是不够方便,因此,提供了下列两个函数以执行二进制 I/O 操作。
#include <stdio.h>
size_t fread(void *restrict ptr, size_t size, size_t nobj,FILE *restrict fp);
size_t fwrite(const void *restrict ptr, size_t size, size_t nobj,FILE *restrict fp);
两者返回:读取或者写入的对象数目。
fread 函数从 fp 中读取 nobj 个内存块,每个内存块大小是 size ,把读取的数据存放在 ptr 指向的位置。如果成功了, fread 会返回读取的内存块数目。如果到达文件结尾或者出错了,会返回一个小于 nobj 的数或者0, fread 是无法区分的,这时候需要使用 feof() 和 ferror() 来进行区分。
fwrite 向 stream 中写 nobj 个内存块,每个内存块大小是 size ,数据来源是 ptr 。如果到达文件结尾或者出错了,会返回一个小于 nobj 的数或者0。
这函数有两个常用的用途:
a)读写一个二进制数组。例如将一个数组中的第2到5个元素写入,那么如下:
float data[10];
if (fwrite(&data[2], sizeof(float), 4, fp) != 4)
printf("fwrite error");
这里,我们指定数组每个元素(浮点类型)的大小,以及元素的数目。
b)读写一个结构,例如:
struct {
short count;
long total;
char name[NAMESIZE];
}item;
if (fwrite(&item, sizeof(item), 1, fp) != 1)
printf("fwrite error");
这里,我们指定结构大小,以及要写入的结构对象的数目(1个)。
注意: fread 和 fwrite 的一个问题是: fread 的数据必须是 fwrite 从同一个系统上面写的。大致因为:
- 不同系统上面,编译环境有可能不同,结构的字节对齐因素等也不同。
- 不同的系统,二进制文件格式可能不同,可能还包含了平台相关的信息。
译者注
原文参考
10、流的定位
有三组方法在 I/O 流中进行定位:
-
ftell和fseek:在大约version7时开始,它们把位置存放在一个长整型中去。 -
ftello和fseeko:在Single UNIX Specification是里面被引入,允许文件的偏移是off_t类型的。 -
fgetpos和fsetpos:由ISO C引入,它使用一个fpos_t类型来存放文件的位置,这个类型可以记载需要的文件大小。
需要移植到非Unix的程序,最好用 fgetpos 和 fsetpos 。
#include <stdio.h>
long ftell(FILE *fp);
返回:如果成功返回当前文件位置标识, 如果错误返回 1L (应该一般是 -1 并且设置 errno )。
int fseek(FILE *fp, long offset, int whence);
返回:如果成功返回0,如果错误返回非0(应该一般是-1)。
void rewind(FILE *fp);
对于一个二进制文件,其位置标识是从文件起始位置开始,并以字节为计量单位。 ftell 用于二进制文件时,返回值就是字节位置。为了用 fseek 定位一个二进制文件,必须指定参数 offset ,以及解释这个参数含义的参数 whence 。 whence 的值与 lseek 函数的相同, SEEK_SET 表示从文件的起始位置开始, SEEK_CUR 表示从当前文件位置计算, SEEK_END 表示从文件的尾端计算。
对于文本文件,文件当前位置可能不以简单的字节位移量来度量。这主要是因为在非UNIX系统中,可能以不同的格式存放文本文件。为了定位一个文本文件, whence 一定要是 SEEK_SET ,而且 offset 只能有两种值:0(表到文件起始位置),或对该文件的 ftell 所返回的值。
使用 rewind 函数也可将一个流设置到文件的起始位置。
下面的函数和前面的 ftell 与 fseek 一样,只是参数类型由 long 变成了 off_t :
#include <stdio.h>
off_t ftello(FILE *fp);
返回:如果成功返回当前文件位置标识, 如果错误返回1( off_t 类型,其值一般为-1)。
int fseeko(FILE *fp, off_t offset, int whence);
返回:如果成功返回0,如果错误返回非0(其值一般为-1)。
因为有些系统实现将 off_t 定义成大于32位的了,所以有了这两个函数。
fgetpos 和 fsetpos 是 ISO C 中引入的,声明如下:
#include <stdio.h>
int fgetpos(FILE *restrict fp, fpos_t *restrict pos);
int fsetpos(FILE *fp, const fpos_t *pos);
两者返回:如果成功返回0, 如果错误返回非0(其值一般为-1)。
fgetpos 将文件位置标识的当前值存入 pos 指向的对象中。在之后调用 fsetpos 时,可以使用此值将流重新定位至该位置。








网友评论