2

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

但是,当您遍历文件时,会调用一个常量READAHEAD_BUFSIZE来定义一次读取多少数据(此常量在此处定义)。

我的问题正是这个buffering论点与READAHEAD_BUFSIZE. 当我遍历一个文件时,哪一个定义了一次从磁盘读取多少数据?C 源代码中是否有一个地方可以说明这一点?

4

2 回答 2

1

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

for line in fileobj:
    print line

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

来自file.next()

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

操作系统缓冲区大小没有改变,setvbuf当文件打开并且文件迭代代码没有触及时完成。相反,调用Py_UniversalNewlineFread(which uses 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()缓冲区和内部预读缓冲区之间的相互作用;而且如果操作系统本身正在使用缓冲。很可能即使文件缓冲区已用尽,操作系统也会为系统调用提供服务,以从其自己的缓存中读取文件,而不是转到物理磁盘。

于 2013-04-13T19:04:55.707 回答
0

在深入挖掘源代码并尝试了解更多如何setvbuffread工作之后,我想我理解了彼此之间的关系和关系:当迭代文件时,每一行都填充了一个缓冲区buffering,但是填充这个缓冲区使用调用,每个都填充一个字节缓冲区。READAHEAD_BUFSIZEREADAHEAD_BUFSIZEfreadbuffering

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 个字节被复制bufbuf2. 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会从磁盘读取字节,但可能更多。我认为第一次迭代将读取 x 字节,其中 x是大于for line in f.x 的最小倍数。bufferingREADAHEAD_BUF_SIZE

如果有人能确认这是实际发生的事情,那就太好了!

于 2013-04-13T20:23:01.353 回答