Аргумент открытой буферизации Python3 выглядит странно - PullRequest
2 голосов
/ 26 марта 2019

Из буферизации doc

- необязательное целое число, используемое для установки политики буферизации.Пропустите 0, чтобы выключить буферизацию (разрешено только в двоичном режиме), 1, чтобы выбрать буферизацию строки (можно использовать только в текстовом режиме), и целое число> 1, чтобы указать размер в байтах буфера фрагмента фиксированного размера.Если аргумент буферизации не задан, политика буферизации по умолчанию работает следующим образом:

Двоичные файлы буферизуются частями фиксированного размера;размер буфера выбирается с помощью эвристики, которая пытается определить «размер блока» базового устройства и использует io.DEFAULT_BUFFER_SIZE.Во многих системах размер буфера обычно составляет 4096 или 8192 байта.«Интерактивные» текстовые файлы (файлы, для которых isatty () возвращает True) используют буферизацию строки.Другие текстовые файлы используют политику, описанную выше для двоичных файлов.

Я открываю файл с именем test.log в текстовом режиме и устанавливаю буферизацию на 16. Поэтому я думаю, что размер фрагмента равен 16, икогда я пишу 32 байта строки в файл.Он будет вызывать write (системный вызов) дважды.Но на самом деле, он вызывается только один раз (тест в Python 3.7.2 GCC 8.2.1 20181127 в Linux)

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?

1 Ответ

1 голос
/ 27 марта 2019

Этот ответ действителен для CPython 3.7. Другие реализации Python могут отличаться.

Функция open() в текстовом режиме возвращает _io.TextIOWrapper()._io.TextIOWrapper() имеет внутренний буфер, называемый pending_bytes с размером 8192 байта (это жестко запрограммированный ), и он также имеет дескриптор на _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() / _io.BufferedRandom(), инициализированный с размером буфераиз buffering параметр.

Давайте рассмотрим несколько примеров.Я начну с более простого, использующего двоичный режим.

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

strace output:

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 байтов приведет к переполнению буфера, поэтому он сначала очищает его (вызывает system 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 output:

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 output:

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

Сначала напомним, что pending_bytes имеет размер 8192B. В первой итерации он записывает 8192 байта (из кода: 'a'*8192) в pending_bytes буфер.Во второй итерации он добавляет к pending_buffer еще 8192 байта и обнаруживает, что он больше 8192 (размер буфера pending_bytes), и записывает его в базовый _io.BufferedWriter().Буфер в _io.BufferedWriter() имеет размер 16 B (параметр buffering), поэтому он будет немедленно записывать в файл (так же, как в случае 2).Теперь pending_buffer пуст и в третьей итерации он снова заполняется 8192 B. В четвертой итерации он добавляет еще одно переполнение буфера 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)

прямой вывод:

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. Add8192 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. Записывает 8192 B из pending_buffer в _io.BufferedWriter() (возможно, из-за 8192 + 16384 < 30000)
  2. Записывает (8192 + 16384 =) 24576 Bв файл.
  3. Закройте дескриптор файла.

Кстати, в настоящее время я понятия не имею, почему существует pending_buffer, когда его можно использовать для буферизации нижележащего буфера из _io.BufferedWritter(). По-моему, он там, потому что он повышает производительность файлов, работающих в текстовом режиме.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...