open() 的缓冲参数和迭代文件时使用的硬编码预读缓冲区大小有啥区别?

Posted

技术标签:

【中文标题】open() 的缓冲参数和迭代文件时使用的硬编码预读缓冲区大小有啥区别?【英文标题】:What is the difference between the buffering argument to open() and the hardcoded readahead buffer size used when iterating through a file?open() 的缓冲参数和迭代文件时使用的硬编码预读缓冲区大小有什么区别? 【发布时间】:2013-04-13 19:02:12 【问题描述】:

受this question 的启发,我想知道Python 的open() 函数的可选缓冲参数究竟是做什么的。通过查看the source,我看到buffering 被传递到setvbuf 以设置流的缓冲区大小(并且它在没有setvbuf 的系统上没有任何作用,文档确认了这一点)。

但是,当您遍历文件时,有一个名为 READAHEAD_BUFSIZE 的常量似乎定义了一次读取多少数据(此常量定义为 here)。

我的问题是 buffering 参数与 READAHEAD_BUFSIZE 的关系究竟如何。当我遍历一个文件时,哪一个定义了一次从磁盘读取多少数据? C 源代码中是否有明确说明这一点的地方?

【问题讨论】:

【参考方案1】:

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

for line in fileobj:
    print line

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

来自file.next()

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

操作系统缓冲区大小未更改,setvbuf 在文件打开且文件迭代代码未触及时完成。相反,调用Py_UniversalNewlineFread(使用fread)来填充预读缓冲区,在Python 内部创建一个second 缓冲区。否则,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() 缓冲区和内部预读缓冲区之间的相互作用;而且如果操作系统本身正在使用缓冲。很可能即使文件缓冲区耗尽,操作系统也会为系统调用提供服务,从它自己的缓存中读取文件,而不是去物理磁盘。

【讨论】:

但是当您调用open 时会调用setvbuf。大概在file_iternext 内部的某个地方调用了一个操作系统级别的读取,所以这个读取覆盖了setvbuf 设置的缓冲区大小,以便使用READAHEAD_BUFSIZE 对不起,我还是不明白。我正在研究两个函数,file_iternextfile_read。两者都通过调用Py_UniversalNewlineFread 从文件中获取数据,传递给它的bufsize 分别等于READAHEAD_BUFSIZE 或文件的大小。 Py_UniversalNewlineFread 然后通过调用fread 读取bufsize 字节。也许fread 没有使用setvbuf 设置的缓冲区,但是我对buffering 参数的意义感到困惑:它为哪些文件操作设置了缓冲区大小? 我想我现在明白了;您的回答很有帮助,但我并没有完全了解 both bufferingREADAHEAD_BUFSIZE 如何影响迭代时读取的字节数。谢谢!【参考方案2】:

在深入挖掘源代码并尝试更多地了解 setvbuffread 的工作原理后,我想我了解了 bufferingREADAHEAD_BUFSIZE 之间的关系:在遍历文件时,a每行都填充READAHEAD_BUFSIZE 的缓冲区,但填充此缓冲区使用对fread 的调用,每个调用都填充buffering 字节的缓冲区。

Python 的read 实现为file_read,它调用Py_UniversalNewlineFread,将要读取的字节数传递给nPy_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 是大于READAHEAD_BUF_SIZEbuffering 的最小倍数。

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

【讨论】:

以上是关于open() 的缓冲参数和迭代文件时使用的硬编码预读缓冲区大小有啥区别?的主要内容,如果未能解决你的问题,请参考以下文章

更改 javascript 文件中的硬编码 ID

python之文件读写详解

EasyRTMP+EasyDSS实现一套完整的紧急视频回传直播与存储回放方案之EasyRTMP-iOS的AACEncoder.m文件实现音频的硬编码功能

通过 findById 访问活动布局中的硬编码片段

如何将脚本转换为 Tcl 语言的硬编码版本?

open和fopen的区别