Как я могу быстро разобрать большие (> 10 ГБ) файлы? - PullRequest
3 голосов
/ 17 декабря 2009

Мне нужно обработать текстовые файлы размером 10-20 ГБ в формате: поле1 поле2 поле3 поле4 поле5

Я хотел бы проанализировать данные из каждой строки поля2 в один из нескольких файлов; файл, в который это помещается, определяется построчно значением в field4. В field2 имеется 25 различных возможных значений и, следовательно, 25 различных файлов, в которые можно анализировать данные.

Я пытался использовать Perl (медленно) и awk (быстрее, но все еще медленно) - есть ли у кого-нибудь какие-либо предложения или указания на альтернативные подходы?

К вашему сведению, это код awk, который я пытался использовать; примечание: мне пришлось вернуться к просмотру большого файла 25 раз, потому что я не смог сохранить 25 открытых файлов одновременно в awk:

chromosomes=(1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25)
for chr in ${chromosomes[@]}
do

awk < my_in_file_here -v pat="$chr" '{if ($4 == pat) for (i = $2; i <= $2+52; i++) print i}' >> my_out_file_"$chr".query 

done

Ответы [ 6 ]

15 голосов
/ 17 декабря 2009

С Perl, откройте файлы во время инициализации и затем сопоставьте выходные данные для каждой строки с соответствующим файлом:

#! /usr/bin/perl

use warnings;
use strict;

my @values = (1..25);

my %fh;
foreach my $chr (@values) {
  my $path = "my_out_file_$chr.query";
  open my $fh, ">", $path
    or die "$0: open $path: $!";

  $fh{$chr} = $fh;
}

while (<>) {
  chomp;
  my($a,$b,$c,$d,$e) = split " ", $_, 5;

  print { $fh{$d} } "$_\n"
    for $b .. $b+52;
}
7 голосов
/ 21 декабря 2009

Вот решение на Python. Я проверил это на небольшом фальшивом файле, который я составил. Я думаю, что это будет приемлемо быстро даже для большого файла, потому что большая часть работы будет выполняться кодом C внутри Python. И я думаю, что это приятная и простая для понимания программа; Я предпочитаю Python Perl.

import sys

s_usage = """\
Usage: csplit <filename>
Splits input file by columns, writes column 2 to file based on chromosome from column 4."""

if len(sys.argv) != 2 or sys.argv[1] in ("-h", "--help", "/?"):

    sys.stderr.write(s_usage + "\n")
    sys.exit(1)


# replace these with the actual patterns, of course
lst_pat = [
    'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j',
    'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't',
    'u', 'v', 'w', 'x', 'y'
]


d = {}
for s_pat in lst_pat:
    # build a dictionary mapping each pattern to an open output file
    d[s_pat] = open("my_out_file_" + s_pat, "wt")

if False:
    # if the patterns are unsuitable for filenames (contain '*', '?', etc.) use this:
    for i, s_pat in enumerate(lst_pat):
        # build a dictionary mapping each pattern to an output file
        d[s_pat] = open("my_out_file_" + str(i), "wt")

for line in open(sys.argv[1]):
    # split a line into words, and unpack into variables.
    # use '_' for a variable name to indicate data we don't care about.
    # s_data is the data we want, and s_pat is the pattern controlling the output
    _, s_data, _, s_pat, _ = line.split()
    # use s_pat to get to the file handle of the appropriate output file, and write data.
    d[s_pat].write(s_data + "\n")

# close all the output file handles.
for key in d:
    d[key].close()

РЕДАКТИРОВАТЬ: Вот немного больше информации об этой программе, так как кажется, что вы будете использовать ее.

Вся обработка ошибок неявная. Если произойдет ошибка, Python выдаст исключение, которое прекратит обработку. Например, если не удается открыть один из файлов, эта программа прекратит выполнение, и Python напечатает обратную трассировку, показывающую, какая строка кода вызвала исключение. Я мог бы обернуть критические части с помощью блока «попробовать / исключить», чтобы ловить ошибки, но для такой простой программы я не видел никакого смысла.

Это тонко, но есть проверка, есть ли ровно пять слов в каждой строке входного файла. Когда этот код распаковывает строку, он делает это в пять переменных. (Имя переменной "_" является допустимым именем переменной, но в сообществе Python существует соглашение использовать его для переменных, которые вам на самом деле не нужны.) Python выдаст исключение, если на входная строка для распаковки в пять переменных. Если ваш входной файл иногда может содержать четыре слова в строке или шесть или более, вы можете изменить программу, чтобы не вызывать исключение; измените основной цикл следующим образом:

for line in open(sys.argv[1]):
    lst = line.split()
    d[lst[3]].write(lst[1] + "\n")

Это разбивает строку на слова, а затем просто назначает весь список слов в одну переменную, lst. Так что этой строке кода не важно, сколько слов в строке. Затем следующая строка индексируется в список, чтобы получить значения. Поскольку Python индексирует список, используя для начала 0, второе слово - lst[1], а четвертое слово - lst[3]. Пока в списке есть как минимум четыре слова, эта строка кода также не будет вызывать исключение.

И, конечно, если четвертого слова в строке нет в словаре файловых дескрипторов, Python также вызовет исключение для этого. Это остановит обработку. Вот некоторый пример кода для того, как использовать блок «попробовать / исключить» для обработки этого:

for line in open(sys.argv[1]):
    lst = line.split()
    try:
        d[lst[3]].write(lst[1] + "\n")
    except KeyError:
        sys.stderr.write("Warning: illegal line seen: " + line)

Удачи в вашем проекте.

РЕДАКТИРОВАТЬ: @larelogio указал, что этот код не соответствует коду AWK. В коде AWK есть дополнительный цикл for, который я не понимаю. Вот код Python для того же:

for line in open(sys.argv[1]):
    lst = line.split()
    n = int(lst[1])
    for i in range(n, n+53):
        d[lst[3]].write(i + "\n")

А вот еще один способ сделать это. Это может быть немного быстрее, но я не проверял это, поэтому я не уверен.

for line in open(sys.argv[1]):
    lst = line.split()
    n = int(lst[1])
    s = "\n".join(str(i) for i in range(n, n+53))
    d[lst[3]].write(s + "\n")

Это строит одну строку со всеми числами для записи, а затем записывает их в один блок. Это может сэкономить время по сравнению с .write() 53 раза.

7 голосов
/ 17 декабря 2009

Вы знаете, почему это медленно? это потому, что вы обрабатываете этот большой файл 25 раз внешней оболочкой для цикла. !!

awk '
$4 <=25 {
    for (i = $2; i <= $2+52; i++){
        print i >> "my_out_file_"$4".query"
    }
}' bigfile
1 голос
/ 22 декабря 2009

Звучит так, будто вы уже в пути, но я просто хотел упомянуть Memory Mapped I / O как огромную помощь при работе с гигантскими файлами. Было время, когда мне приходилось анализировать двоичный файл .5GB с Visual Basic 5 (да) ... импорт API CreateFileMapping позволил мне проанализировать файл (и создать «читабельный» файл объемом в несколько гигов) в минут . И это заняло всего полчаса или около того.

Вот ссылка, описывающая API на платформах Microsoft, хотя я уверен, что MMIO должен быть на любой платформе: MSDN

Удачи!

1 голос
/ 19 декабря 2009

Бывают случаи, когда awk не является ответом.

Бывают также случаи, когда языки сценариев не являются ответом, когда вам просто лучше откусить пулю и перетащить вашу копию K & R и взломать некоторый код на Си.

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

0 голосов
/ 22 декабря 2009

Существуют некоторые предварительные расчеты, которые могут помочь.

Например, вы можете предварительно рассчитать выходы для каждого значения вашего поля2. Признавая, что они 25, как field4:

my %tx = map {my $tx=''; for my $tx1 ($_ .. $_+52) {$tx.="$tx1\n"}; $_=>$tx} (1..25);

Позднее при написании вы можете сделать print {$fh{$pat}} $tx{$base};

...