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_iternext
和 file_read
。两者都通过调用Py_UniversalNewlineFread
从文件中获取数据,传递给它的bufsize
分别等于READAHEAD_BUFSIZE
或文件的大小。 Py_UniversalNewlineFread
然后通过调用fread
读取bufsize
字节。也许fread
没有使用setvbuf
设置的缓冲区,但是我对buffering
参数的意义感到困惑:它为哪些文件操作设置了缓冲区大小?
我想我现在明白了;您的回答很有帮助,但我并没有完全了解 both buffering
和 READAHEAD_BUFSIZE
如何影响迭代时读取的字节数。谢谢!【参考方案2】:
在深入挖掘源代码并尝试更多地了解 setvbuf
和 fread
的工作原理后,我想我了解了 buffering
和 READAHEAD_BUFSIZE
之间的关系:在遍历文件时,a每行都填充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 是大于READAHEAD_BUF_SIZE
的buffering
的最小倍数。
如果有人能确认这是实际发生的事情,那就太好了!
【讨论】:
以上是关于open() 的缓冲参数和迭代文件时使用的硬编码预读缓冲区大小有啥区别?的主要内容,如果未能解决你的问题,请参考以下文章
EasyRTMP+EasyDSS实现一套完整的紧急视频回传直播与存储回放方案之EasyRTMP-iOS的AACEncoder.m文件实现音频的硬编码功能