Python: производительность между строками чтения и строками чтения

#python #python-3.x

#python #python-3.x

Вопрос:

Я знаю общую разницу между строками чтения и строкой чтения файлового объекта. Но мне больше любопытно, чем их производительность отличается друг от друга, поэтому я провел тест здесь.

 import timeit

with open('test.txt', 'w') as f:
    f.writelines('n'.join("Just a test casetJust a test case2tJust a test case3" for i in range(1000000)))

def a1():
    with open('test.txt', 'r') as f:
        for text in f.readlines():
            pass
def a2():
    with open('test.txt', 'r') as f:
        text = f.readline()
        while text:
            text = f.readline()


print(timeit.timeit(a1, number =100))
print(timeit.timeit(a2, number =100))
  
 $python readline_vs_readlines.py
38.410646996984724
35.876863296027295
  

Но почему это так? Я думаю, что ввод-вывод занимает больше времени, поэтому, если вы читаете больше раз, а не считываете его в память за один раз, это занимает больше времени. Итак, из того, что я вижу здесь, почему мы readlines все равно используем? Это стоит нам огромного объема памяти, если файл большой без увеличения скорости?

Комментарии:

1. Какова цель цикла for в первом тесте? Файл уже прочитан.

2. Я while подозреваю, что циклы получают переназначение для каждой строки.

Ответ №1:

На самом деле даже медленнее, когда readline использовался в for цикле:

 import timeit

with open('test.txt', 'w') as fp:
    print(*("Just a test case" for i in range(1000000)), sep='n', file=fp)


def a1():
    with open('test.txt', 'r') as f:
        for _ in f.readlines():
            pass


def a2():
    with open('test.txt', 'r') as f:
        while _ := f.readline():
            pass


def a3():
    with open('test.txt', 'r') as f:
        for _ in iter(f.readline, ''):
            pass


print(timeit.timeit(a1, number=50))
print(timeit.timeit(a2, number=50))
print(timeit.timeit(a3, number=50))
  

вывод:

 10.9471131
10.282239
9.3618919
  

При сравнении в одном и том же for цикле, очевидно a3 , что way быстрее, чем a1 , хотя это аганистический Дзен python.


Причина этого кроется в исходном коде _pyio.py и iobase.c :

Когда iobase.c недоступно _pyio.py , будет использоваться чистый python.

 def readlines(self, hint=None):
    """Return a list of lines from the stream.
    hint can be specified to control the number of lines read: no more
    lines will be read if the total size (in bytes/characters) of all
    lines so far exceeds hint.
    """
    if hint is None or hint <= 0:
        return list(self)
    n = 0
    lines = []
    for line in self:
        lines.append(line)
        n  = len(line)
        if n >= hint:
            break
    return lines
  

Он добавляет каждую прочитанную строку, для которой используется одна и та же механика readline , — он вообще не загружает весь файл.

То же самое относится и к реализации C iobase.c :

 while (1) {
    Py_ssize_t line_length;
    PyObject *line = PyIter_Next(it);
    if (line == NULL) {
        if (PyErr_Occurred()) {
            goto error;
        }
        else
            break; /* StopIteration raised */
    }

    if (PyList_Append(result, line) < 0) {
        Py_DECREF(line);
        goto error;
    }
    line_length = PyObject_Size(line);
    Py_DECREF(line);
    if (line_length < 0) {
        goto error;
    }
    if (line_length > hint - length)
        break;
    length  = line_length;
}
  

Как вы видите, он вызывает PyList_Append добавление результатов в список.


PS Просто напоминание, объединение — это полбеды, так как для конкатенации такого количества строк print рекомендуется использовать sep параметр with file , . Не соединяйте строки там, где это не нужно.

Комментарии:

1. Это то, что я ищу. Итак, отсюда мы можем сказать, что только для повышения производительности мы всегда должны использовать readline в Python?

2. Как вы видите, с точки зрения производительности, использование iter(f.readline, '') в цикле for является самым быстрым — но это нарушает zen — особые случаи недостаточно особенные, чтобы нарушать правила — путем итерации без итератора с помощью iter hack. Только что протестировано на pypy3.6 , и я могу четко сказать, что нет более быстрого способа, если не использовать C реализацию.

3. Даже в readlines документе говорится, что вы можете выполнять итерации, используя readline в первую очередь без readlines . Поэтому я подозреваю, что это обратная совместимость или что-то в этом роде.

Ответ №2:

Readlines считывает весь текст в память перед запуском цикла, одновременно readline автоматически считывая буфер во время цикла. Вот лучшее объяснение сравнения памяти между ними.