受到这个问题的启发,我想知道Python的open()
函数的可选缓冲参数到底是做什么的。从查看源代码,我看到buffering
被传递到setvbuf
以设置流的缓冲区大小(并且它在没有setvbuf
的系统上什么也不做,文档确认)。
然而,当你在一个文件上迭代时,有一个叫做READAHEAD_BUFSIZE
的常量,它似乎定义了一次读取多少数据(这个常量在这里定义)。
我的问题是buffering
的论点是如何与READAHEAD_BUFSIZE
联系起来的。当我遍历一个文件时,哪一个定义了每次从磁盘读取多少数据?在C源中有什么地方说明了这一点吗?
READAHEAD_BUFSIZE
仅在将文件用作迭代器时使用:
for line in fileobj:
print line
它是一个独立于普通buffer参数的缓冲区,由 fread
c 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()
缓冲区和内部预读缓冲区之间的相互作用;而且如果操作系统本身正在使用缓冲。很有可能,即使文件缓冲区耗尽,操作系统也会调用系统从自己的缓存中读取文件,而不是去物理磁盘。
在深入了解源代码并试图更多地了解setvbuf
和fread
的工作方式后,我想我了解buffering
和READAHEAD_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
。所以这最终变成了对fread
的READAHEAD_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
的最小倍数。
如果有人能确认这是实际发生的事情,那将是伟大的!