Как распараллелить мой bash-скрипт для использования с `find`, не сталкиваясь с условиями гонки? - PullRequest
2 голосов
/ 22 марта 2012

Я пытаюсь выполнить команду, подобную этой:

find ./ -name "*.gz" -print -exec ./extract.sh {} \;

Сами файлы gz имеют небольшой размер.В настоящее время мой extract.sh содержит следующее:

# Start delimiter
echo "#####" $1 >> Info
zcat $1 > temp
# Series of greps to extract some useful information
grep -o -P "..." temp >> Info
grep -o -P "..." temp >> Info
rm temp
echo "####" >> Info

Очевидно, что это нельзя распараллелить, потому что, если я запускаю несколько экземпляров extract.sh, они все записывают в один и тот же файл.Что такое умный способ сделать это?

У меня есть 80K gz файлов на машине с огромной мощностью 32 ядра.

Ответы [ 5 ]

1 голос
/ 23 марта 2012

Вы можете использовать xargs для параллельного поиска.--max-procs ограничивает количество выполняемых процессов (по умолчанию 1):

find ./ -name "*.gz" -print | xargs --max-args 1 --max-procs 32 ./extract.sh

В ./extract.sh вы можете использовать mktemp для записи данных из каждого .gz во временный файл, все из которыхпозже может быть объединено:

# Start delimiter
tmp=`mktemp -t Info.XXXXXX`
src=$1
echo "#####" $1 >> $tmp
zcat $1 > $tmp.unzip
src=$tmp.unzip

# Series of greps to extract some useful information
grep -o -P "..." $src >> $tmp
grep -o -P "..." $src >> $tmp
rm $src
echo "####" >> $tmp

Если у вас есть мощная лошадиная сила, вы можете использовать zgrep напрямую, без расстегивания молнии.Но сначала может быть быстрее zcat, если у вас будет много grep с.

В любом случае, позже объедините все в один файл:

cat /tmp/Info.* > Info
rm /tmp/Info.*

Если вы заботитесь о порядкеиз .gz файлов применяется второй аргумент к ./extract.sh:

find files/ -name "*.gz" | nl -n rz | sed -e 's/\t/\n/' | xargs --max-args 2 ...

И в ./extract.sh:

tmp=`mktemp -t Info.$1.XXXXXX`
src=$2
1 голос
/ 22 марта 2012

Предположим (просто для простоты и ясности), что все ваши файлы начинаются с a-z.

Таким образом, вы можете использовать 26 ядер параллельно при запуске последовательности поиска, как указано выше для каждой буквы. Каждому «найти» нужно сгенерировать собственный агрегированный файл

find ./ -name "a*.gz" -print -exec ./extract.sh a {} \; &
find ./ -name "b*.gz" -print -exec ./extract.sh b {} \; &
..
find ./ -name "z*.gz" -print -exec ./extract.sh z {} \;

(извлечение должно быть передано первому параметру для разделения файла назначения "info")

Если вам нужен большой агрегатный файл, просто объедините все агрегатные.

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

Возможно, движение головки жесткого диска будет ограничением, а не производительностью распаковки (CPU).

Но давайте попробуем

1 голос
/ 22 марта 2012

Быстрая проверка в источнике findutils показывает, что find запускает дочерний процесс для каждого exec.Я полагаю, что тогда это продолжается, хотя, возможно, я неверно истолковываю источник.Из-за этого вы уже параллельны, так как ОС будет обрабатывать их через ваши ядра.И благодаря магии виртуальной памяти одни и те же исполняемые файлы будут в основном использовать то же пространство памяти.

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

Чтобы решить эту проблему, все, что вам нужно сделать, это воспользоваться возможностью оболочки создать временный файл (используя tempfile), иметь каждый скриптсоздайте дамп для временного файла, затем поместите каждый скрипт cat временный файл в файл info.Не забудьте удалить временный файл после использования.

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

Tmpfs - это специальная файловая система, которая использует ваш RAM в качестве «дискового пространства».Это займет количество оперативной памяти, которое вы разрешаете, не будет использовать больше, чем нужно из этого количества, и при необходимости перепишется на диск, если оно заполнится.

Для использования:

  1. Создать точку монтирования (мне нравится / mnt / ramdisk или / media / ramdisk)
  2. Редактировать / etc / fstab от имени root
  3. Add tmpfs /mnt/ramdrive tmpfs size=1G 0 0
  4. Запустите umount от имени root, чтобы смонтировать новый ramdrive.Он также будет монтироваться при загрузке.

См. Запись в Википедии на fstab для всех доступных опций.

0 голосов
/ 26 марта 2012

Несколько grep вызовов в extract.sh, вероятно, являются здесь главным узким местом. Очевидная оптимизация - прочитать каждый файл только один раз, а затем распечатать сводку в нужном вам порядке. В качестве дополнительного преимущества мы можем предположить, что отчет может быть записан как один блок, но это не может полностью предотвратить чередующийся вывод. Тем не менее, вот моя попытка.

#!/bin/sh

for f; do
    zcat "$f" |
    perl -ne '
        /(pattern1)/ && push @pat1, $1;
        /(pattern2)/ && push @pat2, $1;
        # ...
        END { print "##### '"$1"'\n";
            print join ("\n", @pat1), "\n";
            print join ("\n", @pat2), "\n";
            # ...
            print "#### '"$f"'\n"; }'
done

Выполнение этого в awk вместо Perl может быть немного более эффективным, но, поскольку вы используете grep -P Я полагаю, полезно иметь возможность сохранять тот же синтаксис регулярных выражений.

Сценарий принимает несколько .gz файлов в качестве входных данных, поэтому вы можете использовать find -exec extract.sh {} \+ или xargs для запуска ряда параллельных процессов. С помощью xargs вы можете попытаться найти баланс между последовательными и параллельными заданиями, подавая каждый новый процесс, скажем, от 100 до 500 файлов в одном пакете. Вы экономите на количестве новых процессов, но проигрываете в распараллеливании. Некоторые эксперименты должны показать, каким должен быть баланс, но в этот момент я просто вытащу число из своей шляпы и посмотрю, достаточно ли это уже хорошо.

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

0 голосов
/ 22 марта 2012

Я бы создал временный каталог.Затем создайте выходной файл для каждого grep (на основе имени обрабатываемого им файла).Файлы, созданные в /tmp, находятся на RAM-диске, поэтому жесткий диск не будет перегружен большим количеством записей.

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

Пример:

working_dir="`pwd`"
temp_dir="`mktemp -d`"
cd "$temp_dir"
find "$working_dir" -name "*.gz" | xargs -P 32 -n 1 extract.sh 
cat *.output > "$working_dir/Info"
rm -rf "$temp_dir"

extract.sh

 filename=$(basename $1)
 output="$filename.output"
 extracted="$filename.extracted"
 zcat "$1" > "$extracted"

 echo "#####" $filename > "$output"
 # Series of greps to extract some useful information
 grep -o -P "..." "$extracted" >> "$output"
 grep -o -P "..." "$extracted" >> "$output"
 rm "$extracted"
 echo "####" >> "$output"
...