В чем разница между буферным аргументом 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,

Если кто-то может подтвердить, что это действительно так, это было бы здорово!

Другие вопросы по тегам