2016-10-06 2 views
4

Какой самый быстрый способ объединить несколько файлов в столбце wise (внутри Python)?Самый быстрый способ конкатенировать несколько файлов column wise - Python

Предположим, что у меня есть два файла с 1 000 000 000 строк и ~ 200 символов UTF8 в строке.

Метод 1: Обман с paste

Я мог бы объединить эти два файла под систему Linux с помощью paste в оболочке, и я мог обмануть с помощью os.system, то есть:

def concat_files_cheat(file_path, file1, file2, output_path, output): 
    file1 = os.path.join(file_path, file1) 
    file2 = os.path.join(file_path, file2) 
    output = os.path.join(output_path, output) 
    if not os.path.exists(output): 
     os.system('paste ' + file1 + ' ' + file2 + ' > ' + output) 

Метод 2 : Использование диспетчера вложенных контекстов с zip:

def concat_files_zip(file_path, file1, file2, output_path, output): 
    with open(output, 'wb') as fout: 
     with open(file1, 'rb') as fin1, open(file2, 'rb') as fin2: 
      for line1, line2 in zip(fin1, fin2): 
       fout.write(line1 + '\t' + line2) 

Метод 3: Использование fileinput

ли fileinput перебирать файлы параллельно? Или они будут последовательно перебирать каждый файл после другого?

Если это бывшая, я бы предположить, что это будет выглядеть следующим образом:

def concat_files_fileinput(file_path, file1, file2, output_path, output): 
    with fileinput.input(files=(file1, file2)) as f: 
     for line in f: 
      line1, line2 = process(line) 
      fout.write(line1 + '\t' + line2) 

Метод 4: Рассматривайте их как csv

with open(output, 'wb') as fout: 
    with open(file1, 'rb') as fin1, open(file2, 'rb') as fin2: 
     writer = csv.writer(w) 
     reader1, reader2 = csv.reader(fin1), csv.reader(fin2) 
     for line1, line2 in zip(reader1, reader2): 
      writer.writerow(line1 + '\t' + line2) 

Учитывая размер данных, которые были бы быстрейший?

Почему бы вам не выбрать один из них? Могу ли я потерять или добавить информацию?

Для каждого метода, как бы выбрать другой разделитель, отличный от , или \t?

Существуют ли другие способы достижения той же колонны конкатенации? Они так же быстры?

+5

... вы пытались профиль? Ответ на этот вопрос, вероятно, зависит от версии оборудования, ОС и python ... – Bakuriu

+0

Кажется, что 'fileinput' читает файл последовательно и не читает файлы параллельно. – alvas

+0

Метод 1 взял 2.098e-05, а Method2 взял 2.914 ... Нет ли сопоставимой эквивалентности «обману»? – alvas

ответ

0

Вы можете попробовать испытать вашу функцию с помощью timeit. Это может быть полезно doc .

Или же волшебная функция %%timeit в ноутбуке Jupyter. Вам просто нужно написать %%timeit func(data), и вы получите ответ с оценкой своей функции. Этот paper мог бы помочь вам в этом.

2

Вы можете заменить петлю for с writelines пропусканием genexp к нему и заменить zip с izip из itertools в методе 2. Это может приблизиться к paste или превзойти его.

with open(file1, 'rb') as fin1, open(file2, 'rb') as fin2, open(output, 'wb') as fout: 
    fout.writelines(b"{}\t{}".format(*line) for line in izip(fin1, fin2)) 

Если вы не хотите вставлять \t в строке формата, вы можете использовать repeat от itertools;

fout.writelines(b"{}{}{}".format(*line) for line in izip(fin1, repeat(b'\t'), fin2)) 

Если файлы имеют одинаковую длину, вы можете избавиться от izip.

with open(file1, 'rb') as fin1, open(file2, 'rb') as fin2, open(output, 'wb') as fout: 
    fout.writelines(b"{}\t{}".format(line, next(fin2)) for line in fin1) 
0

Метод №1 является самым быстрым, поскольку он использует собственный (вместо Python) код для конкатенации файлов. Однако это окончательно обманывает.

Если вы хотите обмануть, вы можете также рассмотреть возможность написания собственного расширения C для Python - это может быть еще быстрее, в зависимости от ваших навыков кодирования.

Я боюсь, что метод №4 не будет работать, поскольку вы объединяете списки со строкой. Вместо этого я бы пошел с writer.writerow(line1 + line2). Вы можете использовать параметр delimiter как csv.reader, так и csv.writer для настройки разделителя (см. https://docs.python.org/2/library/csv.html).

2

Из всех четырех методов я бы взял второй. Но вы должны позаботиться о мелких деталях в реализации. (с некоторыми улучшениями требуется 0.002 секунд Между тем первоначальная реализация занимает около 6 секунд; файл, в котором я работал, был 1M строк, но не должно быть большой разницы, если файл в 1K раз больше, чем мы не используя почти память).

Изменение от первоначальной реализации:

  • Используйте итераторы, если это возможно, в противном случае потребление памяти будет штрафоваться, и вы должны обработать весь файл сразу. (в основном, если вы используете python 2, вместо использования zip используйте itertools.izip)
  • Когда вы объединяете строки, используйте «% s% s» .format() или аналогичные; в противном случае вы каждый раз генерируете один новый экземпляр строки.
  • Нет необходимости писать строки за строкой внутри. Вы можете использовать итератор внутри записи.
  • Небольшие буферы очень интересны, но если мы используем итераторы, разница очень мала, но если мы попытаемся собрать все данные сразу (так, например, мы помещаем f1.readlines (1024 * 1000), это намного медленнее).

Пример:

def concat_iter(file1, file2, output): 
    with open(output, 'w', 1024) as fo, \ 
     open(file1, 'r') as f1, \ 
     open(file2, 'r') as f2: 
     fo.write("".join("{}\t{}".format(l1, l2) 
      for l1, l2 in izip(f1.readlines(1024), 
           f2.readlines(1024)))) 

оригинальное решение: Профили.

Мы видим, что самая большая проблема заключается в записи и zip (в основном, для того, чтобы не использовать итераторы и иметь возможность обрабатывать/обрабатывать весь файл в памяти).

~/personal/python-algorithms/files$ python -m cProfile sol_original.py 
10000006 function calls in 5.208 seconds 

Ordered by: standard name 

ncalls tottime percall cumtime percall filename:lineno(function) 
    1 0.000 0.000 5.208 5.208 sol_original.py:1(<module>) 
    1 2.422 2.422 5.208 5.208 sol_original.py:1(concat_files_zip) 
    1 0.000 0.000 0.000 0.000 {method 'disable' of '_lsprof.Profiler' objects} 
    **9999999 1.713 0.000 1.713 0.000 {method 'write' of 'file' objects}** 
    3 0.000 0.000 0.000 0.000 {open} 
    1 1.072 1.072 1.072 1.072 {zip} 

Profiler:

~/personal/python-algorithms/files$ python -m cProfile sol1.py 
    3731 function calls in 0.002 seconds 

Ordered by: standard name 

    ncalls tottime percall cumtime percall filename:lineno(function) 
    1 0.000 0.000 0.002 0.002 sol1.py:1(<module>) 
    1 0.000 0.000 0.002 0.002 sol1.py:3(concat_iter6) 
1861 0.001 0.000 0.001 0.000 sol1.py:5(<genexpr>) 
    1 0.000 0.000 0.000 0.000 {method 'disable' of '_lsprof.Profiler' objects} 
1860 0.001 0.000 0.001 0.000 {method 'format' of 'str' objects} 
    1 0.000 0.000 0.002 0.002 {method 'join' of 'str' objects} 
    2 0.000 0.000 0.000 0.000 {method 'readlines' of 'file' objects} 
    **1 0.000 0.000 0.000 0.000 {method 'write' of 'file' objects}** 
    3 0.000 0.000 0.000 0.000 {open} 

И в Python 3 еще быстрее, потому что итераторы встроенные и нам не нужно импортировать любую библиотеку.

~/personal/python-algorithms/files$ python3.5 -m cProfile sol2.py 
843 function calls (842 primitive calls) in 0.001 seconds 
[...] 

А также очень приятно видеть, потребление памяти и файловая система получает доступ, что подтверждает то, что мы говорили ранее:

$ /usr/bin/time -v python sol1.py 
Command being timed: "python sol1.py" 
User time (seconds): 0.01 
[...] 
Maximum resident set size (kbytes): 7120 
Average resident set size (kbytes): 0 
Major (requiring I/O) page faults: 0 
Minor (reclaiming a frame) page faults: 914 
[...] 
File system outputs: 40 
Socket messages sent: 0 
Socket messages received: 0 


$ /usr/bin/time -v python sol_original.py 
Command being timed: "python sol_original.py" 
User time (seconds): 5.64 
[...] 
Maximum resident set size (kbytes): 1752852 
Average resident set size (kbytes): 0 
Major (requiring I/O) page faults: 0 
Minor (reclaiming a frame) page faults: 427697 
[...] 
File system inputs: 0 
File system outputs: 327696 
+0

Вы не можете передать genexp или listcomp в 'file.write'. –

+0

@NizamMohamed Я не знаю, почему вы это сказали. Код компилируется и работает намного быстрее, чем любая другая версия. Если вы посмотрите поближе, я не передам какой-либо genexp/listcomp ... есть «.join (...). –

+0

Спасибо за разъяснение, я был неправ. –

Смежные вопросы