【问题标题】:Does Python have a 8KiB bytes long file I/O cache?Python 是否有 8KiB 字节长的文件 I/O 缓存?
【发布时间】:2018-01-03 08:40:24
【问题描述】:

我正在研究 Python 3.6.0 中的文件 I/O 性能。鉴于此脚本包含 3 个测试:

#!python3

import random, string, time

strs = ''.join(random.choice(string.ascii_lowercase) for i in range(1000000))
strb = bytes(strs, 'latin-1')

inf = open('bench.txt', 'w+b')
inf.write(strb)

for t in range(3):
    inf.seek(0)
    inf.read(8191)

for t in range(3):
    inf.seek(0)
    inf.read(8192)

for t in range(3):
    inf.seek(0)
    inf.read(8193)

inf.close()

Procmon 看到以下操作正在发生(井号行是我的 cmets):

  # Initial write
Offset: 0, Length: 1.000.000
  # The 3 8191-long reads only produce one syscall due to caching:
Offset: 0, Length: 8.192
  # However, if the read length is exactly 8192, python doesn't take advantage:
Offset: 0, Length: 8.192
Offset: 0, Length: 8.192
Offset: 0, Length: 8.192
  # Due to caching, the first syscall of the first read of the last loop is missing.
Offset: 8.192, Length: 8.192
Offset: 0, Length: 8.192
Offset: 8.192, Length: 8.192
Offset: 0, Length: 8.192
Offset: 8.192, Length: 8.192
 # Afterwards, 2 syscalls per read are produced on the 8193-long reads.

首先,很明显 python 会读取 8KiB 的倍数的文件。

我怀疑 python 实现了一个缓存缓冲区,用于存储最后读取的 8KiB 块,如果您尝试连续多次读取相同的 8KiB 范围,它将简单地返回并裁剪它。

有人可以确认python确实实现了这种机制吗?

如果是这种情况,这意味着如果您不以某种方式手动使缓存无效,python 将无法检测到外部应用程序对该块所做的更改。那是对的吗?也许有办法禁用这个机制?

(可选)为什么恰好 8192 字节的读取不能从缓存中受益?

【问题讨论】:

  • 真正想做什么?如果您想确认实际行为,请阅读源代码™。
  • 当你打开一个文件时,有一个可选的buffer参数,这里默认使用操作系统默认的缓冲模式,但是你可以指定它应该是无缓冲的。

标签: python windows python-3.x caching file-io


【解决方案1】:

是的,默认缓冲区大小为 8k。见io.DEFAULT_BUFFER_SIZE

io.DEFAULT_BUFFER_SIZE
int 包含模块缓冲 I/O 类使用的默认缓冲区大小。 open() 尽可能使用文件的blksize(由os.stat() 获得)。

>>> import io
>>> io.DEFAULT_BUFFER_SIZE
8192

还有module source code:

#define DEFAULT_BUFFER_SIZE (8 * 1024)  /* bytes */

如果您使用BufferedIOBase interface 或包装器对文件进行更改,缓冲区将自动更新(以二进制模式打开文件会生成BufferedIOBase 子类,BufferedReaderBufferedWriterBufferedRandom)。

对于您的第二种情况,您的 seek() 调用会刷新该缓冲区,因为您在“当前”块范围之外寻找(当前位置在 8192,第二个缓冲块的第一个字节,您寻找回0,这是第一个缓冲块的第一个字节)。见source code of BufferedIOBase.seek() for more details

如果您需要从其他进程编辑底层文件,使用seek() 是确保在尝试再次读取之前删除缓冲区的好方法,您可以忽略缓冲区并通过BufferedIOBase.raw attribute 转到底层RawIOBase implementation

【讨论】:

  • 目前st_blksize 在 Windows 上不受支持。从 Windows 8 开始,等效信息在 FileStorageInfo 中通过 GetFileInformationByHandleEx 提供为 PhysicalBytesPerSectorForPerformance
  • @eryksun:不知道你为什么要告诉我 ;-) 这应该是 Python 代码开发人员需要思考的问题,不是吗?
  • 引用的文档指出“open() 尽可能使用文件的blksize(由os.stat() 获得)”。我的评论提供了目前在 Windows 上无法进行这种优化的原因,以及程序可以通过 Windows 8+ 中的 ctypes、cffi、Cython、PyWin32 等手动实现它的方式。
猜你喜欢
  • 2013-12-16
  • 1970-01-01
  • 1970-01-01
  • 2010-10-16
  • 1970-01-01
  • 1970-01-01
  • 2015-11-28
  • 2011-12-18
  • 2011-10-06
相关资源
最近更新 更多