Почему быстрее читать файл без разрывов строк?
В Python 3.6 требуется больше времени для чтения файла, если есть разрывы строк. Если у меня есть два файла: один с разрывами строк и один без разрывов строк (но в противном случае они имеют один и тот же текст), тогда файл с разрывами строк займет около 100-200% времени для чтения. Я привел конкретный пример.
Шаг # 1: создайте файлы
sizeMB = 128
sizeKB = 1024 * sizeMB
with open(r'C:\temp\bigfile_one_line.txt', 'w') as f:
for i in range(sizeKB):
f.write('Hello World!\t'*73) # There are roughly 73 phrases in one KB
with open(r'C:\temp\bigfile_newlines.txt', 'w') as f:
for i in range(sizeKB):
f.write('Hello World!\n'*73)
Шаг # 2: Прочитайте файл с одной строкой и временем
IPython
%%timeit
with open(r'C:\temp\bigfile_one_line.txt', 'r') as f:
text = f.read()
Выход
1 loop, best of 3: 368 ms per loop
Шаг №3: Прочитайте файл со многими строками и временем
IPython
%%timeit
with open(r'C:\temp\bigfile_newlines.txt', 'r') as f:
text = f.read()
Выход
1 loop, best of 3: 589 ms per loop
Это только один пример. Я тестировал это для разных ситуаций, и они делают то же самое:
- Различные размеры файлов от 1 МБ до 2 ГБ.
- Использование file.readlines() вместо file.read()
- Использование пробела вместо закладки ('\ t') в файле с одной строкой (т.е. "Hello World!" )
Я пришел к выводу, что файлы с новыми строками ('\n') занимают больше времени, чем файлы без них. Однако я ожидал бы, что к всем персонажам будут относиться одинаково. Это может иметь важные последствия для производительности при чтении большого количества файлов. Кто-нибудь знает, почему это происходит?
Я использую Python 3.6.1, Anaconda 4.3.24 и Windows 10.
Ответы
Ответ 1
Когда вы открываете файл в Python в текстовом режиме (по умолчанию), он использует то, что он называет "универсальными новостями" (представлен с PEP 278, но несколько позже изменился с выпуском Python 3). Что универсальные символы новой строки означают, что независимо от того, какие символы новой строки используются в файле, вы увидите только \n
в Python. Таким образом, файл, содержащий foo\nbar
, будет выглядеть так же, как файл, содержащий foo\r\nbar
или foo\rbar
(поскольку \n
, \r\n
и \r
- это все соглашения о завершении строки, используемые в некоторых операционных системах за какое-то время).
Логика, которая обеспечивает эту поддержку, вероятно, является причиной ваших различий в производительности. Даже если символы \n
в файле не преобразуются, код должен изучить их более тщательно, чем символы, отличные от новой строки.
Я подозреваю, что разница в производительности, которую вы видите, исчезнет, если вы откроете свои файлы в двоичном режиме, где такая поддержка новой строки не предоставляется. Вы также можете передать параметр newline
в open
в Python 3, который может иметь различные значения в зависимости от того, какое значение вы даете. Я не знаю, какое влияние может оказать какое-то конкретное значение на производительность, но, возможно, стоит проверить, действительно ли разница в производительности, которую вы видите, имеет значение для вашей программы. Я бы попробовал пропустить newline=""
и newline="\n"
(или что-то вроде того, что заканчивается обычная строка вашей платформы).
Ответ 2
Однако, я ожидал бы, что все символы будут обработаны одинаково.
Ну, это не так. Разрыв линии является особенным.
Разрывы строк не всегда представлены как \n
. Причины - длинная история, относящаяся к ранним дням физических телепринтеров, которые я не буду вдаваться здесь, но где эта история закончилась тем, что Windows использует \r\n
, Unix использует \n
и классическую Mac OS используется \r
.
Если вы откроете файл в текстовом режиме, разрывы строк, используемые файлом, будут переведены на \n
, когда вы их прочтете, а \n
будет переведен в соглашение об отключении строки ОС при написании. В большинстве языков программирования это обрабатывается на лету кодом уровня ОС и довольно дешево, но Python делает все по-другому.
У Python есть функция универсальные новые строки, где она пытается обрабатывать все соглашения о разрыве строк, независимо от того, на какой ОС вы находитесь. Даже если файл содержит комбинацию разрывов строк \r
, \n
и \r\n
, Python распознает их все и переведет их на \n
. Универсальные новые строки включены по умолчанию в Python 3, если вы не настроили конкретное соглашение о завершении строки с аргументом newline
на open
.
В универсальном режиме новой строки реализация файла должна считывать файл в двоичном режиме, проверять содержимое для \r\n
символов и
построить новый строковый объект с переводом строк
если он найдет окончание строк \r
или \r\n
. Если он находит только \n
endings или если он вообще не находит окончаний строки, ему не нужно выполнять передачу или строить новый строковый объект.
Построение новой строки и перевод строк завершено. Читая файл с вкладками, Python не должен выполнять перевод.
Ответ 3
В Windows открытие в текстовом режиме преобразует символы '\n'
в '\r\n'
при записи, а при чтении - наоборот.
Итак, я немного экспериментировал. Я сейчас на MacOS, поэтому моя "родная" строка заканчивается '\n'
, поэтому я подготовил аналогичный тест для вашего, за исключением использования не-родных строк Windows:
sizeMB = 128
sizeKB = 1024 * sizeMB
with open(r'bigfile_one_line.txt', 'w') as f:
for i in range(sizeKB):
f.write('Hello World!!\t'*73) # There are roughly 73 phrases in one KB
with open(r'bigfile_newlines.txt', 'w') as f:
for i in range(sizeKB):
f.write('Hello World!\r\n'*73)
И результаты:
In [4]: %%timeit
...: with open('bigfile_one_line.txt', 'r') as f:
...: text = f.read()
...:
1 loop, best of 3: 141 ms per loop
In [5]: %%timeit
...: with open('bigfile_newlines.txt', 'r') as f:
...: text = f.read()
...:
1 loop, best of 3: 543 ms per loop
In [6]: %%timeit
...: with open('bigfile_one_line.txt', 'rb') as f:
...: text = f.read()
...:
10 loops, best of 3: 76.1 ms per loop
In [7]: %%timeit
...: with open('bigfile_newlines.txt', 'rb') as f:
...: text = f.read()
...:
10 loops, best of 3: 77.4 ms per loop
Очень похоже на ваш, и обратите внимание, что разница в производительности исчезает, когда я открываю в двоичном режиме. Хорошо, что, если вместо этого я использую * nix line-endings?
with open(r'bigfile_one_line_nix.txt', 'w') as f:
for i in range(sizeKB):
f.write('Hello World!\t'*73) # There are roughly 73 phrases in one KB
with open(r'bigfile_newlines_nix.txt', 'w') as f:
for i in range(sizeKB):
f.write('Hello World!\n'*73)
И результаты с использованием этого нового файла:
In [11]: %%timeit
...: with open('bigfile_one_line_nix.txt', 'r') as f:
...: text = f.read()
...:
10 loops, best of 3: 144 ms per loop
In [12]: %%timeit
...: with open('bigfile_newlines_nix.txt', 'r') as f:
...: text = f.read()
...:
10 loops, best of 3: 138 ms per loop
Ага! Разница в производительности исчезает! Так что да, я думаю, что использование не-родных строк-концов влияет на производительность, что имеет смысл, учитывая поведение текстового режима.