оптимизация sed (модификация большого файла на основе меньшего набора данных)

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

моя последняя задача включает в себя редактирование строк на основе данных из другого файла.

файл данных (который должен быть изменен) содержит 1500000 строк, каждая из которых, например, 800 символов. Каждая строка уникальна и содержит только один идентификационный номер, каждый идентификационный номер уникален)

модификатор файл, например, 1800 строк, содержит идентификационный номер, а также сумму и дату, которые должны быть изменены в файле данных.

Я только что преобразовал (с помощью Vim regex) файл модификатора в sed, но он очень неэффективен.

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

(some 500 character)id_number(some 300 character)

и мне нужно изменить данные в части 300 символов.

на основе файла модификатора я придумываю такие строки sed:

/id_number/ s/^(.{650}).{20}/CHANGED_AMOUNT_AND_DATA/

Так я 1800 таких строк.

но я знаю, что даже на очень быстром сервере, если я делаю

sed -i.bak -f modifier.sed data.file

Это очень медленно, потому что он должен читать каждый шаблон x каждую строку.

разве нет лучшего способа?

Примечание: я не программист, никогда не учился (в школе) алгоритмам. Я могу использовать awk, sed, устаревшую версию perl на сервере.

6 ответов


мои предлагаемые подходы (в порядке желательности) заключались бы в обработке этих данных как:

  1. база данных (даже простая БД на основе SQLite с индексом будет работать намного лучше, чем sed/awk в файле 10GB)
  2. плоский файл, содержащий записи фиксированной длины
  3. плоский файл, содержащий переменные длины записей

использование базы данных заботится обо всех тех мелких деталях, которые замедляют обработку текстовых файлов (поиск нужной записи о, изменение данных, сохранение их обратно в БД). Посмотрите на DBD:: SQLite в случае Perl.

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

Если у вас есть переменные длины записи, я бы предложил преобразовать в фиксированные длины записи (так как это появляется только ВАШ ID переменной длины). Если вы не можете этого сделать, возможно, какие-либо существующие данные никогда не будут перемещаться в файле? Затем вы можете поддерживать этот ранее упомянутый индекс и добавлять новые записи по мере необходимости, с той разницей, что вместо индекса, указывающего на номер записи, Теперь вы указываете на абсолютную позицию в файле.


Я предлагаю вам программу, написанную на Perl (поскольку я не гуру sed/awk, и я не то, на что они точно способны).

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

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

Я тоже не гуру Perl, но я думаю, что программа довольно проста. Если вам нужна помощь, чтобы написать его, попросите его : -)


С perl вы должны использовать substr для получения id_number, особенно если id_number имеет постоянную ширину.

my $id_number=substr($str, 500, id_number_length);

после этого, если $id_number находится в диапазоне, вы должны использовать substr для замены оставшегося текста.

substr($str, -300,300, $new_text);

регулярные выражения Perl очень быстрые, но не в этом случае.


мое предложение: не используйте базу данных. Хорошо написанный скрипт perl будет превосходить базу данных по порядку величины в такого рода задачах. Поверьте, у меня большой практический опыт. Вы не импортируете данные в базу данных, когда perl будет завершен.

когда вы пишете 1500000 строк с 800 символами, мне кажется, 1,2 ГБ. Если у вас будет очень медленный диск (30 МБ/с), вы прочитаете его за 40 секунд. С лучшими 50 - > 24s, 100 - > 12s и так далее. Но поиск хэша perl (например, db присоединиться) скорость на 2GHz CPU выше 5Mlookups / s. Это означает, что ваша работа с процессором будет в секундах, а работа с IO-в десятках секунд. Если это действительно 10Gb номера будут меняться, но пропорция такая же.

вы не указали, изменяет ли изменение данных размер или нет (если изменение может быть сделано на месте), поэтому мы не будем предполагать это и будем работать как фильтр. Вы не указали, какой формат вашего "файла-модификатора" и какая модификация. Предположим, что это разделены вкладкой что-то вроде:

<id><tab><position_after_id><tab><amount><tab><data>

мы будем читать данные из stdin и писать в stdout и сценарий может быть что-то вроде этого:

my $modifier_filename = 'modifier_file.txt';

open my $mf, '<', $modifier_filename or die "Can't open '$modifier_filename': $!";
my %modifications;
while (<$mf>) {
   chomp;
   my ($id, $position, $amount, $data) = split /\t/;
   $modifications{$id} = [$position, $amount, $data];
}
close $mf;

# make matching regexp (use quotemeta to prevent regexp meaningful characters)
my $id_regexp = join '|', map quotemeta, keys %modifications;
$id_regexp = qr/($id_regexp)/;     # compile regexp

while (<>) {
  next unless m/$id_regexp/;
  next unless $modifications{};
  my ($position, $amount, $data) = @{$modifications{}};
  substr $_, $+[1] + $position, $amount, $data;
}
continue { print }

на моем ноутбуке требуется около половины минуты для 1,5 миллионов строк, 1800 идентификаторов поиска, 1,2 ГБ данных. Для 10GB это не должно быть более 5 минут. Это разумно быстро для вас?

Если вы начинаете думать, что вы не привязаны к IO (например, если используете NAS), но привязаны к CPU, вы можете пожертвовать некоторой удобочитаемостью и изменить на это:

my $mod;
while (<>) {
  next unless m/$id_regexp/;
  $mod = $modifications{};
  next unless $mod;
  substr $_, $+[1] + $mod->[0], $mod->[1], $mod->[2];
}
continue { print }

вы почти наверняка должны использовать базу данных, как MikeyB предложил.

Если вы по какой-то причине не хотите использовать базу данных, то если список модификаций поместится в память (как это будет в настоящее время в 1800 строках), наиболее эффективным методом является хэш-таблица, заполненная модификациями, предложенными Ив baumes подключено.

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

  1. сравниваем идентификатор на "вершине" входной файл с ID в "топовой" модификации файла
  2. отрегулируйте запись соответственно, если они соответствуют
  3. писать
  4. отбросьте "верхнюю" строку из любого файла с (алфавитно или численно) самым низким идентификатором и Прочитайте другую строку из этого файла
  5. Goto 1.

за сцены, база данных почти наверняка будет использовать слияние списка, если вы выполните это изменение с помощью одного SQL .


хорошая сделка по решению sqlloader или datadump. Вот как надо поступить.