Как я могу быстро разобрать большие (> 10 ГБ) файлы?

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

Я хотел бы разобрать данные из каждой строки field2 в один из нескольких файлов; файл, в который это помещается, определяется построчно значением в 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

person Andrew    schedule 17.12.2009    source источник
comment
Когда вы говорите медленно, можете уточнить? возможно, укажите какой-то диапазон, который вы хотели бы завершить? Парсинг 10 ГБ текста — довольно большая работа.   -  person GrayWizardx    schedule 17.12.2009
comment
пожалуйста, покажите образец файла, где это возможно   -  person ghostdog74    schedule 17.12.2009


Ответы (6)


Вот решение на 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 напечатает трассировку, показывающую, какая строка кода вызвала исключение. Я мог бы обернуть критические части блоком try/except, чтобы отлавливать ошибки, но для такой простой программы я не видел никакого смысла.

Это тонко, но есть проверка, чтобы увидеть, есть ли ровно пять слов в каждой строке входного файла. Когда этот код распаковывает строку, он делает это в пять переменных. (Имя переменной «_» является допустимым именем переменной, но в сообществе 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 и в этом случае вызовет исключение. Это остановило бы обработку. Вот пример кода, как использовать блок «try/except» для обработки этого:

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 раза.

person steveha    schedule 21.12.2009
comment
steveha - большое спасибо за это - я никогда не пробовал python, но это работает хорошо - я все еще вижу ~ два часа или около того, чтобы обработать этот файл, что несколько похоже на время, которое он занимал awk. Я предполагаю, что мне, возможно, придется сделать решительный шаг и следовать ответу Джона Строма, который должен попробовать C - действительно вне моего опыта, но я попробую xmas 1ch1g0 - спасибо за это - я пошел по пути просматривая файл так много раз, потому что я не мог держать открытыми 26 файлов одновременно - awk выдавал ошибку; возможно, мне следовало попробовать gawk или что-то подобное. Счастливых праздников. - person Andrew; 21.12.2009
comment
steveha - обновление - я говорил слишком рано - ваше решение на python на самом деле работает примерно через 30 минут - это будет работать очень хорошо для меня, я очень ценю помощь. - person Andrew; 21.12.2009
comment
Я рад, что смог помочь. Я обновил свой ответ дополнительной информацией; Надеюсь, вам будет полезна дополнительная информация. Успехов вашему проекту и хороших праздников! - person steveha; 21.12.2009
comment
Эта программа производит правильный вывод? Похоже, что это будет отличаться от оригинального предложения в awk. - person larelogio; 22.12.2009
comment
Хммм... Я работал с текстом вверху, объясняя проблему. Глядя на код AWK, я очень запутался. Что это за цикл делает? Почему он печатает лишние числа? Что ж, какого черта, я отредактирую свой ответ и предоставлю код, соответствующий AWK. - person steveha; 22.12.2009
comment
@steveha: этот цикл отвечает за часть времени обработки и большую часть размера вывода. :( - person larelogio; 23.12.2009
comment
@larelogio, предположительно файлы, сгенерированные этой программой, будут обработаны позже. Если бы вы могли сгенерировать 53 последовательных значения как часть последующей обработки, это могло бы упростить процесс. Но если есть смысл генерировать их из этого, что ж, это достаточно просто, и я предоставил код. С праздником всех! - person steveha; 23.12.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;
}
person Greg Bacon    schedule 17.12.2009

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

awk '
$4 <=25 {
    for (i = $2; i <= $2+52; i++){
        print i >> "my_out_file_"$4".query"
    }
}' bigfile
person ghostdog74    schedule 17.12.2009
comment
У вас есть непревзойденная цитата в спецификации выходного файла. - person Dennis Williamson; 17.12.2009
comment
Кроме того, он увеличивает pat для каждой строки, поэтому прекращает что-либо делать после 26-й строки. Вместо этого вам нужен вложенный for loops. - person Dennis Williamson; 17.12.2009
comment
спасибо, что поймал. что касается вашего второго комментария, я подожду, пока OP покажет свой входной файл, прежде чем вносить изменения. переменная pat может быть увеличена с использованием определенных шаблонов в файле, которые обозначают следующую итерацию. - person ghostdog74; 17.12.2009
comment
Вы так и не определили слово «пат». Я думаю, ты хочешь print i >> "my_out_file_"$4".query" - person SiegeX; 22.12.2009

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

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

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

person John R. Strohm    schedule 19.12.2009
comment
-1: когда задействован большой ввод-вывод, C не помогает. Он скорее поработает над алгоритмом (например, прочитает файл один раз, а не 25 раз), чем переключит язык! - person Davide; 22.12.2009

Похоже, вы уже в пути, но я просто хотел упомянуть ввод-вывод с отображением памяти как огромную помощь при работе с гигантскими файлами. Было время, когда мне приходилось разбирать двоичный файл размером 0,5 ГБ с помощью Visual Basic 5 (да)... импорт CreateFileMapping API позволил мне разобрать файл (и создать несколько гигабайтных «удобочитаемых» файлов) в минут. А на реализацию ушло всего полчаса.

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

Удачи!

person user236359    schedule 21.12.2009

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

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

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

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

person larelogio    schedule 22.12.2009