Разница между itertools.islice и функцией чтения - PullRequest
0 голосов
/ 19 мая 2019

Я пытаюсь лучше понять, как обрабатывать файлы с миллионами записей при минимальном использовании памяти.

На практике я создал файл с ~ 6,5 миллионами строк и написал пару функций, чтобы разбить его на ~ 7 файлов по миллиону строк в каждом.В первой функции я использовал метод чтения файла python, чтобы создать логику так, чтобы новый файл создавался после считывания 1 миллиона строк, пока мы не дойдем до последнего файла, в который записаны оставшиеся 500К строк.

Тофункция запускается навсегда.

Затем я создал другую функцию для разделения файла с помощью itertools.islice.Это заняло чуть менее ~ 2 секунд, чтобы бежать.

Теперь я знаю, что islice - это итератор, который выполняет итерации файлового объекта, поэтому ожидается, что он будет более эффективным в использовании памяти.Однако чем он отличается от метода read ()?

Я думал, что даже read () проходит каждую строку в файле одну за другой (вроде как итератор ...).Итак, я ожидал, что производительность двух программ будет одинаковой.Не могли бы вы, ребята, помочь мне понять, почему islice НАМНОГО быстрее?

Вот оба фрагмента кода -

1 с использованием read () -

with open("bigfile.txt","r") as f:
    filenum = 1
    j = 1
    for i, line in enumerate(f):
        if j <= 1000000:
            with open("big_out_%d" % filenum, "a") as outfile:
                outfile.write(line)
        j += 1
        if j == 1000000:
            j = 1
            filenum += 1
            with open("big_out_%d" % filenum, "a") as outfile:
                outfile.write(line)

2 с использованием islice-

import itertools
import time

start = time.time()

with open("bigfile.txt","r") as f:
    i = 1
    while True:
        chunk = list(itertools.islice(f, 1000000))
        if not chunk:
            print "reached the end"
            break
        with open("out%d.txt" % i, "w") as out:
            out.writelines(chunk)
        print i
        i += 1

end = time.time()
print "time is %d" % ((end-start))

Ответы [ 2 ]

3 голосов
/ 19 мая 2019

Разница не имеет ничего общего с islice и read (). Ваши две программы сильно отличаются по своей логике.

В первом листинге вы перебираете строки файла. На каждой итерации цикла вы открываете файл, пишете одну строку, а затем снова закрываете файл. (Синтаксис «с открытым» приводит к закрытию файла в конце блока with:). К тому времени, когда вы закончите, вы записали 6500000 строк в семь разных файлов, но вы также выполнили 6500000 открытий файлов и 6500000 закрытий файлов. Я не удивлен, что ОС не может сделать это эффективно.

Во втором листинге вы читаете фрагмент из 1000000 строк, а затем пишете все это один раз. Вы по-прежнему пишете 6500000 строк, но здесь вы выполняете 7 открытий и 7 закрытий. Совсем не одно и то же.

Использование with: для выходных файлов делает ваш первый список очень неуклюжим. Попробуйте это:

with open("bigfile.txt","r") as f:
    filenum = 1
    j = 1
    outfile = open("big_out_%d" % filenum, "w")
    try:
        for line in f:
            outfile.write(line)
            j += 1
            if j == 1000000:
                outfile.close()
                j = 1
                filenum += 1
                outfile = open("big_out_%d" % filenum, "w")
    finally:
        outfile.close()

Я не тестировал этот код. Если в этом есть ошибка, ее легко исправить.

При таком подходе вы никогда не загружаете в память более одной строки за раз.

0 голосов
/ 19 мая 2019

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

Вы можете попробовать третью версию кода, которая является частью ваших текущих двух версий. Он по-прежнему читает и пишет отдельные строки, но между выходами сохраняет открытый файл вывода:

with open("bigfile.txt","r") as f:
    outfile = None
    for i, line in enumerate(f):
        if i % 1000000 == 0:
            if outfile:
                outfile.close()
            outfile = open("big_out_%d" % (i // 1000000), "w")
        outfile.write(line)
    if outfile:
        outfile.close()

Обратите внимание, что я немного упростил задачу, используя индекс i из enumerate для всего подсчета, а не вручную обновляя целые числа filenum или j в вашем первом коде. Это вряд ли окажет какое-либо существенное влияние на производительность, но делает код намного приятнее.

...