open()的buffer参数与在遍历文件时使用的硬编码预读缓冲区大小之间的区别是什么?



受到这个问题的启发,我想知道Python的open()函数的可选缓冲参数到底是做什么的。从查看源代码,我看到buffering被传递到setvbuf以设置流的缓冲区大小(并且它在没有setvbuf的系统上什么也不做,文档确认)。

然而,当你在一个文件上迭代时,有一个叫做READAHEAD_BUFSIZE的常量,它似乎定义了一次读取多少数据(这个常量在这里定义)。

我的问题是buffering的论点是如何与READAHEAD_BUFSIZE联系起来的。当我遍历一个文件时,哪一个定义了每次从磁盘读取多少数据?在C源中有什么地方说明了这一点吗?

READAHEAD_BUFSIZE 仅在将文件用作迭代器时使用:

for line in fileobj:
    print line

它是一个独立于普通buffer参数的缓冲区,由 freadc API调用处理。两者都在迭代时使用。

From file.next():

为了使for循环成为遍历文件行最有效的方式(一种非常常见的操作),next()方法使用了一个隐藏的预读缓冲区。由于使用预读缓冲区,将next()与其他文件方法(如readline())结合使用无法正常工作。但是,使用seek()将文件重新定位到绝对位置将刷新预读缓冲区。

操作系统缓冲区大小不改变,setvbuf是在文件被打开并且不被文件迭代代码触及时完成的。相反,调用Py_UniversalNewlineFread(它使用fread)被用来填充预读缓冲区,在Python内部创建一个缓冲区。否则,Python将常规缓冲留给C API调用(fread()调用被缓冲;fread()会参考用户空间缓冲区来满足请求,Python不需要做任何事情)。

readahead_get_line_skip()然后从这个缓冲区提供行(换行结束)。如果缓冲区不再包含换行符,它将用1.25倍于前一个值的缓冲区大小递归重新填充缓冲区。这意味着如果整个文件中没有换行字符,那么文件迭代可以将文件的其余部分读入内存缓冲区!

要查看缓冲区读取多少,在循环时打印文件位置(使用fileobj.tell()):

>>> with open('test.txt') as f:
...     for line in f:
...         print f.tell()
... 
8192   # 1 times the buffer size
8192
8192
~ lines elided
18432  # + 1.25 times the buffer size
18432
18432
~ lines elided
26624  # + 1 times the buffer size; the last newline must've aligned on the buffer boundary
26624
26624
~ lines elided
36864  # + 1.25 times the buffer size
36864
36864

等。

实际从磁盘读取的字节数(假设fileobj是磁盘上的实际物理文件)不仅取决于fread()缓冲区和内部预读缓冲区之间的相互作用;而且如果操作系统本身正在使用缓冲。很有可能,即使文件缓冲区耗尽,操作系统也会调用系统从自己的缓存中读取文件,而不是去物理磁盘。

在深入了解源代码并试图更多地了解setvbuffread的工作方式后,我想我了解bufferingREADAHEAD_BUFSIZE如何相互关联:当遍历文件时,每行填充READAHEAD_BUFSIZE的缓冲区,但填充此缓冲区使用对fread的调用,每个调用填充buffering字节的缓冲区。

Python的read是通过file_read实现的,它调用Py_UniversalNewlineFread,将要读取的字节数作为n传递给它。Py_UniversalNewlineFread然后最终调用fread读取n字节。

当你迭代一个文件时,readahead_get_line_skip函数是用来检索一行的。这个函数也调用Py_UniversalNewlineFread,传递n = READAHEAD_BUFSIZE。所以这最终变成了对freadREADAHEAD_BUFSIZE字节的调用。

那么现在的问题是,fread实际从磁盘读取了多少字节。如果我在C中运行以下代码,那么1024字节被复制到buf, 512字节被复制到buf2。(这可能是显而易见的,但从来没有使用过setvbuf之前,它是一个有用的实验对我来说)

FILE *f = fopen("test.txt", "r");
void *buf = malloc(1024);
void *buf2 = mallo(512);
setvbuf(f, buf, _IOFBF, 1024);
fread(buf2, 512, 1, f);
所以,最后,这对我来说表明,当在文件上迭代时,至少从磁盘读取 READAHEAD_BUF_SIZE字节,但可能更多。我认为for line in f的第一次迭代将读取x字节,其中x是buffering大于READAHEAD_BUF_SIZE的最小倍数。

如果有人能确认这是实际发生的事情,那将是伟大的!

最新更新