从doc
buffering是用于设置缓冲策略的可选整数。 传递0以关闭缓冲(仅在二进制模式下允许),传递1至 选择行缓冲(仅在文本模式下可用),并且整数> 1 指示固定大小的块缓冲区的大小(以字节为单位)。当没有 给定buffering参数,默认的缓冲策略为 如下:
二进制文件以固定大小的块缓冲;使用试探法来确定缓冲区的大小来选择缓冲区的大小 设备的“块大小”,然后返回io.DEFAULT_BUFFER_SIZE。上 在许多系统中,缓冲区的长度通常为4096或8192字节。 “交互式”文本文件(isatty()返回True的文件)使用行缓冲。其他文本文件使用上述策略 用于二进制文件。
我以文本模式打开了一个名为test.log
的文件,并将缓冲设置为16。因此,当我向该文件写入32个字节的字符串时,我认为块大小为16。它将调用write
(系统调用)两次。但不幸的是,它只调用一次。(在Linux 3.7.2 GCC 8.2.1 20181127上进行测试)
import os
try:
os.unlink('test.log')
except Exception:
pass
with open('test.log', 'a', buffering=16) as f:
for _ in range(10):
f.write('a' * 32)
使用strace -e write python3 test.py
跟踪syscall并获得关注
write(3, "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"..., 320) = 320
buffering
是什么意思?
答案 0 :(得分:1)
此答案对CPython 3.7有效,其他Python实现可能有所不同。
文本模式下的open()
函数返回_io.TextIOWrapper()
。 _io.TextIOWrapper()
的内部“缓冲区”名为pending_bytes
,大小为8192字节(为hard coded),并且在文本模式_io.BufferedWriter()
的{{1}}上也有句柄或w
(对于文本模式_io.BufferedRandom()
)。 a
/ _io.BufferedWriter()
的大小由_io.BufferedRandom()
函数中的参数buffering
指定。
当您调用open()
时,它将把文本添加到内部_io.TextIOWrapper().write("some text")
缓冲区中。写完之后,您将填充pending_bytes
缓冲区,然后将其写入pending_bytes
内部的缓冲区。当您也填满_io.BufferedWriter()
内的缓冲区时,它将被写入目标文件。
以二进制模式打开文件时,您将直接从缓冲区_io.BufferedWriter()
参数中初始化_io.BufferedWriter()
/ _io.BufferedRandom()
对象。
让我们看一些示例。我将从使用二进制模式的简单模式开始。
buffering
strace输出:
# Case 1
with open('test.log', 'wb', buffering=16) as f:
for _ in range(5):
f.write(b'a'*15)
在第一次迭代中,它用15个字节填充缓冲区。在第二次迭代中,它发现再添加15个字节将使缓冲区溢出,因此首先刷新缓冲区(调用系统write(3, "aaaaaaaaaaaaaaa", 15) = 15
write(3, "aaaaaaaaaaaaaaa", 15) = 15
write(3, "aaaaaaaaaaaaaaa", 15) = 15
write(3, "aaaaaaaaaaaaaaa", 15) = 15
write(3, "aaaaaaaaaaaaaaa", 15) = 15
),然后保存这些新的15个字节。在下一次迭代中,同样的情况再次发生。在缓冲区中的最后一次迭代之后是15 B,它们被写在文件结尾处(离开write
上下文)。
第二种情况,我将尝试将比缓冲区大小更多的数据写入缓冲区:
with
strace输出:
# Case 2
with open('test.log', 'wb', buffering=16) as f:
for _ in range(5):
f.write(b'a'*17)
这里发生的是,在第一次迭代中,它将尝试写入缓冲区17 B,但无法放入缓冲区17 B,因此将其直接写入文件,并且缓冲区保持为空。这适用于每次迭代。
现在让我们看看文本模式。
write(3, "aaaaaaaaaaaaaaaaa", 17) = 17
write(3, "aaaaaaaaaaaaaaaaa", 17) = 17
write(3, "aaaaaaaaaaaaaaaaa", 17) = 17
write(3, "aaaaaaaaaaaaaaaaa", 17) = 17
write(3, "aaaaaaaaaaaaaaaaa", 17) = 17
strace输出:
# Case 3
with open('test.log', 'w', buffering=16) as f:
for _ in range(5):
f.write('a'*8192)
首先回想一下write(3, "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"..., 16384) = 16384
write(3, "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"..., 16384) = 16384
write(3, "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"..., 8192) = 8192
的大小为8192B。在第一次迭代中,它将8192字节(来自代码:pending_bytes
)写入'a'*8192
缓冲区。在第二次迭代中,它向pending_bytes
添加了另一个8192字节,发现它大于8192(pending_buffer
缓冲区的大小),并将其写入基础pending_bytes
中。 _io.BufferedWriter()
中的缓冲区的大小为16 B(_io.BufferedWriter()
参数),因此它将立即写入文件(与情况2相同)。现在buffering
为空,并且在第三次迭代中再次填充了8192B。在第四次迭代中,它又添加了8192 B pending_buffer
缓冲区溢出,并且如第二次迭代一样再次直接写入文件。在上一次迭代中,它将8192 B添加到pending_bytes
缓冲区中,该缓冲区在文件关闭时被刷新。
最后一个示例包含大于8192 B的缓冲区。另外,为了更好地说明,我又添加了2个迭代。
pending_bytes
strace输出:
# Case 4
with open('test.log', 'w', buffering=30000) as f:
for _ in range(7):
f.write('a'*8192)
迭代次数:
write(3, "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"..., 16384) = 16384
write(3, "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"..., 16384) = 16384
write(3, "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"..., 24576) = 24576
中。pending_bytes
中添加8192 B,但它大于最大大小,因此将其写入基础pending_bytes
并停留在其中(_io.BufferedWritter()
现在为空)。pending_bytes
中。pending_bytes
中,但它大于最大大小,因此它尝试写入基础pending_bytes
中。但这会超出基础缓冲区的最大容量,原因是_io.BufferedWritter()
(迭代2中仍然存在第一个16384 B),因此它首先将旧的16384 B写入文件,然后将那些新的16384 B(来自{{1} })放入缓冲区。 (现在16384 + 16384 > 30000
缓冲区再次为空)pending_bytes
为空,pending_bytes
包含16384B。在此迭代中,它用8192 B填充了pending_buffer
。就是这样。程序离开_io.BufferedWritter()
部分时,它将关闭文件。关闭过程如下:
pending_buffer
写入with
(可能是pending_buffer
的原因)_io.BufferedWriter()
=)24576 B写入文件。当前我不知道为什么8192 + 16384 < 30000
可以用来缓冲来自8192 + 16384
的基础缓冲区。最好的猜测是它存在,因为它可以提高以文本模式工作的文件的性能。