Python3的开放缓冲参数看起来很奇怪

时间:2019-03-26 15:33:05

标签: python-3.x io python-3.7

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是什么意思?

1 个答案:

答案 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)

迭代次数:

  1. 将8192 B添加到write(3, "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"..., 16384) = 16384 write(3, "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"..., 16384) = 16384 write(3, "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"..., 24576) = 24576 中。
  2. pending_bytes中添加8192 B,但它大于最大大小,因此将其写入基础pending_bytes并停留在其中(_io.BufferedWritter()现在为空)。
  3. 将8192 B添加到pending_bytes中。
  4. 将8192 B添加到pending_bytes中,但它大于最大大小,因此它尝试写入基础pending_bytes中。但这会超出基础缓冲区的最大容量,原因是_io.BufferedWritter()(迭代2中仍然存在第一个16384 B),因此它首先将旧的16384 B写入文件,然后将那些新的16384 B(来自{{1} })放入缓冲区。 (现在16384 + 16384 > 30000缓冲区再次为空)
  5. 与3相同
  6. 与4相同
  7. 当前pending_bytes为空,pending_bytes包含16384B。在此迭代中,它用8192 B填充了pending_buffer。就是这样。

程序离开_io.BufferedWritter()部分时,它将关闭文件。关闭过程如下:

  1. 将8192 B从pending_buffer写入with(可能是pending_buffer的原因)
  2. 将(_io.BufferedWriter() =)24576 B写入文件。
  3. 关闭文件描述符。

当前我不知道为什么8192 + 16384 < 30000可以用来缓冲来自8192 + 16384的基础缓冲区。最好的猜测是它存在,因为它可以提高以文本模式工作的文件的性能。