Python3 open buffering参数看起来很奇怪

问题描述 投票:2回答:1

来自doc

buffering是一个可选的整数,用于设置缓冲策略。传递0以切换缓冲关闭(仅允许在二进制模式下),1选择行缓冲(仅在文本模式下可用),并且整数> 1以指示固定大小的块缓冲区的大小(以字节为单位)。如果没有给出缓冲参数,则默认缓冲策略的工作方式如下:

二进制文件以固定大小的块缓冲;使用启发式方法选择缓冲区的大小,尝试确定底层设备的“块大小”并回退到io.DEFAULT_BUFFER_SIZE。在许多系统上,缓冲区通常为4096或8192字节长。 “交互式”文本文件(isatty()返回True的文件)使用行缓冲。其他文本文件使用上述策略用于二进制文件。

我用文本模式打开一个名为test.log的文件,并将缓冲设置为16.所以我认为块大小是16,当我向文件写入32字节字符串时。它会两次调用write(系统调用)。但实际上,它只调用一次。(在Linux上测试Python 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跟踪系统调用,并获得以下内容

write(3, "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"..., 320) = 320

buffering是什么意思?

python-3.x io python-3.7
1个回答
1
投票

这个答案对CPython 3.7有效,其他Python实现可能有所不同。

文本模式中的open()函数返回_io.TextIOWrapper()_io.TextIOWrapper()有内部'缓冲区',称为pending_bytes,大小为8192字节(它是hard coded),它还有_io.BufferedWriter()处理文本模式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() parametr的缓冲区大小初始化的_io.BufferedRandom() / buffering对象。

我们来看一些例子。我将从更简单的使用二进制模式开始。

# Case 1
with open('test.log', 'wb', buffering=16) as f:
    for _ in range(5):
        f.write(b'a'*15)

strace输出:

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个字节将溢出缓冲区,因此它首先刷新它(调用系统write)然后保存那些新的15个字节。在下一次迭代中,同样会再次发生。在缓冲区中的最后一次迭代之后是15 B,它们在文件结束时写入(留下with上下文)。

第二种情况,我会尝试写入缓冲区比缓冲区大小更多的数据:

# Case 2
with open('test.log', 'wb', buffering=16) as f:
    for _ in range(5):
        f.write(b'a'*17) 

strace输出:

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

这里发生的是在第一次迭代中它将尝试写入缓冲区17 B但它不适合那里,因此它直接写入文件并且缓冲区保持为空。这适用于每次迭代。

现在让我们看一下文本模式。

# Case 3
with open('test.log', 'w', buffering=16) as f:
    for _ in range(5):
        f.write('a'*8192)

strace输出:

write(3, "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"..., 16384) = 16384
write(3, "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"..., 16384) = 16384
write(3, "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"..., 8192) = 8192

首先回想起pending_bytes的大小为8192 B.在第一次迭代中,它将8192个字节(来自代码:'a'*8192)写入pending_bytes缓冲区。在第二次迭代中,它将另外的8192个字节添加到pending_buffer,并发现它超过8192(pending_bytes缓冲区的大小)并将其写入底层的_io.BufferedWriter()_io.BufferedWriter()中的缓冲区大小为16 B(buffering参数),因此它会立即写入文件(与案例2相同)。现在pending_buffer是空的,并且在第三次迭代中它再次填充8192B。在第四次迭代中,它再添加8192个B pending_bytes缓冲区溢出,并且它再次直接写入文件,如第二次迭代。在最后一次迭代中,它将8192 B添加到pending_bytes缓冲区中,该缓冲区在文件关闭时刷新。

最后一个示例包含大于8192 B的缓冲。另外为了更好的解释,我再添加了2次迭代。

# Case 4
with open('test.log', 'w', buffering=30000) as f:
    for _ in range(7):
        f.write('a'*8192)

strace输出:

write(3, "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"..., 16384) = 16384
write(3, "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"..., 16384) = 16384
write(3, "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"..., 24576) = 24576

迭代:

  1. 将8192 B加入pending_bytes
  2. 将8192 B添加到pending_bytes但它超过最大大小,因此它被写入底层_io.BufferedWritter()并且它保持在那里(pending_bytes现在是空的)。
  3. 将8192 B加入pending_bytes
  4. 将8192 B添加到pending_bytes中,但它超过了最大值,因此它试图写入底层的_io.BufferedWritter()。但它会超过底层缓冲区的最大容量导致16384 + 16384 > 30000(第一次16384 B仍然存在于迭代2中)所以它首先将旧的16384 B写入文件,然后将这些新的16384 B(来自pending_bytes)放入缓冲区。 (现在再次pending_bytes缓冲区为空)
  5. 与3相同
  6. 与4相同
  7. 目前pending_buffer是空的,_io.BufferedWritter()包含16384 B.在这个迭代中,它填充pending_buffer与8192 B.这就是它。

当程序离开with部分时,它关闭文件。关闭过程如下:

  1. pending_buffer写入8192 B到_io.BufferedWriter()(有可能导致8192 + 16384 < 30000
  2. 将(8192 + 16384 =)24576 B写入文件。
  3. 关闭文件描述符。

顺便说一下,目前我不知道为什么有pending_buffer它可以用来缓冲来自_io.BufferedWritter()的底层缓冲区。我最好的猜测就是它,因为它可以提高文本模式下工作的性能。

© www.soinside.com 2019 - 2024. All rights reserved.