В чем разница между буферным аргументом open() и жестко заданным размером буфера readahead, используемым при итерации по файлу?
Вдохновленный этим вопросом, мне интересно, каков необязательный аргумент буферизации для Python open()
функция делает. Глядя на источник, я вижу, что buffering
передается в setvbuf
установить размер буфера для потока (и что он ничего не делает в системе без setvbuf
, что подтверждают документы).
Однако, когда вы перебираете файл, появляется константа READAHEAD_BUFSIZE
это, кажется, определяет, сколько данных читается за один раз (эта константа определена здесь).
Мой вопрос, как именно buffering
аргумент относится к READAHEAD_BUFSIZE
, Когда я перебираю файл, который определяет, сколько данных считывается с диска за раз? И есть ли место в источнике C, которое проясняет это?
2 ответа
READAHEAD_BUFSIZE
используется только при использовании файла в качестве итератора:
for line in fileobj:
print line
Это отдельный буфер от обычного аргумента буфера, который обрабатывается fread
C API вызывает. Оба используются при итерации.
От file.next()
:
Для того, чтобы сделать
for
цикл наиболее эффективный способ зацикливания строк файла (очень распространенная операция),next()
Метод использует скрытый буфер опережающего чтения. Как следствие использования буфера упреждающего чтения, объединениеnext()
с другими методами файла (например,readline()
) не работает правильно. Однако, используяseek()
чтобы переместить файл в абсолютную позицию, очистится буфер опережающего чтения.
Размер буфера ОС не изменяется, setvbuf
выполняется, когда файл открыт и не затронут кодом итерации файла. Вместо этого звонки Py_UniversalNewlineFread
(который использует fread
) используются для заполнения буфера упреждающего чтения, создавая второй буфер внутри Python. В противном случае Python оставляет обычную буферизацию до вызовов C API (fread()
звонки буферизуются; с буфером пространства пользователя обращаются fread()
чтобы удовлетворить запрос, Python ничего не должен делать с этим).
readahead_get_line_skip()
затем обслуживает строки (новая строка прекращается) из этого буфера. Если в буфере больше нет строк новой строки, он будет заполнять буфер, возвращаясь к нему с размером буфера, в 1,25 раза превышающим предыдущее значение. Это означает, что итерация файла может считывать весь оставшийся файл в буфер памяти, если во всем файле больше нет символов новой строки!
Чтобы увидеть, сколько читает буфер, напечатайте позицию файла (используя fileobj.tell()
) пока цикл:
>>> with open('test.txt') as f:
... for line in f:
... print f.tell()
...
8192 # 1 times the buffer size
8192
8192
~ lines elided
18432 # + 1.25 times the buffer size
18432
18432
~ lines elided
26624 # + 1 times the buffer size; the last newline must've aligned on the buffer boundary
26624
26624
~ lines elided
36864 # + 1.25 times the buffer size
36864
36864
и т.п.
Какие байты фактически читаются с диска (при условии fileobj
фактический физический файл на вашем диске) зависит не только от взаимодействия между fread()
буфер и внутренний буфер опережающего чтения; но также, если сама ОС использует буферизацию. Вполне возможно, что даже если файловый буфер исчерпан, операционная система выполняет системный вызов для чтения из файла из своего собственного кэша, а не на физический диск.
После того, как копаться в источнике немного больше и пытаться понять, как setvbuf
а также fread
работать, я думаю, я понимаю, как buffering
а также READAHEAD_BUFSIZE
связаны друг с другом: при переборе файла, буфер READAHEAD_BUFSIZE
заполняется в каждой строке, но заполнение этого буфера использует вызовы fread
каждый из которых заполняет буфер buffering
байт.
Питона read
реализован как file_read, который вызывает Py_UniversalNewlineFread, передавая ему количество байтов для чтения как n
, Py_UniversalNewlineFread
потом звонит fread
читать n байтов.
Когда вы перебираете файл, функция readahead_get_line_skip - это то, что извлекает строку. Эта функция также вызывает Py_UniversalNewlineFread
, проходя n = READAHEAD_BUFSIZE
, Так что это в конечном итоге становится призывом к fread
за READAHEAD_BUFSIZE
байт.
Итак, теперь вопрос в том, сколько байтов fread
на самом деле читать с диска. Если я запускаю следующий код в C, то 1024 байта копируются в buf
и 512 в buf2
, (Это может быть очевидно, но никогда не использовал setvbuf
раньше это был полезный эксперимент для меня.)
FILE *f = fopen("test.txt", "r");
void *buf = malloc(1024);
void *buf2 = mallo(512);
setvbuf(f, buf, _IOFBF, 1024);
fread(buf2, 512, 1, f);
Итак, наконец, это говорит мне о том, что при переборе файла, по крайней мере READAHEAD_BUF_SIZE
байты читаются с диска, но это может быть больше. Я думаю, что первая итерация for line in f
будет читать х байтов, где х является наименьшим кратным buffering
это больше чем READAHEAD_BUF_SIZE
,
Если кто-то может подтвердить, что это действительно так, это было бы здорово!