Как оптимизировать эту команду Unix?

Как оптимизировать эту команду Unix?

Следующая команда выводит результат примерно за 10 минут.

find . -name "muc*_*_20160920_*.unl*" | xargs zcat |
    awk -F "|" '{if($14=="20160920100643" && $22=="567094398953") print $0}'| head

Как можно улучшить его производительность?

решение1

Это уже довольно оптимизировано. Трудно понять, в чем заключается узкое место, не зная больше деталей, таких как:

  • тип хранилища (HD, SSD, сетевой, RAID-массив)
  • количество и средний размер совпадающих файлов
  • количество каталогов и других несовпадающих файлов
  • количество полей в каждой строке
  • средняя длина строки

Что можно сделать в любом случае:

  • замените -print | xargsна -exec cmd {} +или , -print0 | xargs -r0если ваш find/ xargsподдерживает это. -print | xargsне только неправильно, но и более затратно, так как xargsнеобходимо декодировать символы, чтобы выяснить, какие из них являются пробелами, и выполнить некоторую дорогостоящую обработку цитат.
  • исправить локаль на C ( export LC_ALL=C). Поскольку все задействованные здесь символы ( |и десятичные цифры для содержимого файла и латинские буквы, точка и подчеркивание для имен файлов) являются частью переносимой кодировки, если ваша кодировка — UTF-8 или какая-то другая многобайтовая кодировка, переключение на C с ее однобайтовой кодировкой сэкономит много работы для findи awk.
  • упростим awkчасть до: awk -F "|" '$14 == "20160920100643" && $22 == "567094398953"'.
  • так как вы передаете вывод в head, вы можете захотеть отключить буферизацию вывода для awk, чтобы он выводил эти 10 строк как можно раньше. С gawkили mawk, вы можете использовать fflush()для этого. Или вы можете добавить if (++n == 10) exitin awk.

Подводить итоги:

(export LC_ALL=C
find . -name "muc*_*_20160920_*.unl*" -exec zcat {} + |
  awk -F "|" '$14 == "20160920100643" && $22 == "567094398953" {
    print; if (++n == 10) exit}')

Если узким местом является ЦП, на многоядерной системе GNU можно попробовать:

(export LC_ALL=C
find . -name "muc*_*_20160920_*.unl*" -print0 |
  xargs -r0P 4 -n 100 sh -c '
    zcat "$@" | 
      awk -F "|" "\$14 == "20160920100643" && \$22 == "567094398953" {
        print; fflush()}"' sh | head)

zcat | awkДля параллельного выполнения 4 заданий по 100 файлов в пакетах.

Если это 20160920100643временная метка, вы можете исключить файлы, которые были изменены в последний раз до этого. С GNU или BSD findдобавьте -newermt '2016-09-20 10:06:42'.

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

grep -E '^([^|]*\|){13}20160920100643(\|[^|]*){7}\|567094398953(\||$)'

вместо awkкоманды. С помощью GNU grepдобавьте --line-bufferedвозможность выводить строки как можно раньше при параллельном подходе или -m 10останавливаться после 10 совпадений при непараллельном.

Подводя итог, если центральный процессор является узким местом, а в вашей системе не менее 4 ядер ЦП, имеется не менее 400 файлов muc* и вы используете систему GNU (которая grepобычно значительно быстрее, чем GNU awk):

(export LC_ALL=C
find . -name "muc*_*_20160920_*.unl*" -newermt '2016-09-20 10:06:42' -print0 |
  xargs -r0P 4 -n 100 sh -c '
    zcat "$@" | 
      grep --line-buffered -E \
        "^([^|]*\|){13}20160920100643(\|[^|]*){7}\|567094398953(\||$)"
  ' sh | head)

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

решение2

Ответ @Stéphane Chazelas содержит множество подробностей о том, как можно оптимизировать конвейер команд.

find . -name "muc*_*_20160920_*.unl*" | xargs zcat |
    awk -F "|" '{if($14=="20160920100643" && $22=="567094398953") print $0}'| head

Я собираюсь предоставить другой способ подхода к проблеме, когда вы на самом деле измеряете, где вы тратите больше всего времени. Как только вы найдете, где тратится время, вы сможете определить, что с этим делать. Если вы хотите улучшить время 10-минутного бега, оптимизация шага, который занимает 2 секунды, почти бесполезна.

Когда я смотрю на конвейер команд, мое внимание привлекают три вещи:

  1. find .- Какова структура каталогов? Сколько файлов в каталоге? Является ли каталог локальным для системы, на которой выполняется команда? Удаленная файловая система будетмногопомедленнее.
  2. -name "muc*_*_20160920_*.unl*"- Насколько близки все имена файлов в структуре каталогов? Все ли они «близки» к имени и сложно/интенсивно нагружают процессор для сопоставления? Потому чтокаждыйИмя файла в дереве каталогов должно быть считано с диска и сравнено с шаблоном.
  3. xargs zcat- xargsМне не кажется, что это будет слишком большой проблемой производительности, особенно по сравнению с findпроблемами выше и zcatсамим собой. Даже если это 10 000 или даже 10 000 000 имен файлов, время, потраченное на передачу и разбор только имен, почти наверняка пренебрежимо мало по сравнению со временем, потраченнымнаходкаимена, а затем открытие и распаковка всех файлов. Насколько велики файлы? Потому что вы распаковываете всекаждыйфайл, соответствующий findшаблону имени вашего файла.

Как определить, в чем заключается основная проблема производительности? Измерьте производительность каждой команды в конвейере. (См.https://stackoverflow.com/questions/13294554/how-to-use-gnu-time-with-pipeline(Для получения подробной информации о хронометраже всего конвейера.) Вы можете выполнить следующие команды и посмотреть, сколько времени каждый шаг вносит во время обработки всего конвейера:

/usr/bin/time find .- Это говорит вам, сколько времени требуется для прохождения по дереву каталогов. Если это происходит медленно, вам нужна лучшая система хранения. Очистите кэш файловой системыпрежде чем засекать время, чтобы получить худший случай измерения, затем снова запустите timed findи посмотрите, насколько кэширование влияет на производительность. И если каталог не является локальным, попробуйте запустить команду в реальной системе, в которой находятся файлы.

/usr/bin/time find . -name "muc*_*_20160920_*.unl*"- Это скажет вам, сколько времени займет сопоставление имен файлов с шаблоном. Снова очистите кэш файловой системы и запустите его дважды.

/usr/bin/time bash -c "find . -name 'muc*_*_20160920_*.unl*' | xargs zcat > /dev/null"- Я подозреваю, что это основной компонент долгого времени работы вашего конвейера. Если проблема в этом, то распараллеливание zcatкоманд по ответу Стефана Шазеласа может быть лучшим ответом.

Продолжайте добавлять шаги из исходного конвейера команд в тот, который тестируется, пока не обнаружите, где вы тратите большую часть времени. Опять же, я подозреваю, что это шаг zcat. Если так, возможно, zcatпараллелизация, которую разместил @Stéphane Chazelas, поможет.

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

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

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

решение3

Как отмечено в комментарияхzgrepявляется лучшим выбором для такого рода задач сглобстаропция, которая позволяет использовать **какall path inside the directory except hidden

shopt -s globstar
zgrep -m 10 '^\([^|]*|\)\{13\}20160920100643|\([^|]*|\)\{7\}567094398953' ./**muc*_*_20160920_*.unl*
shopt -u globstar

решение4

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

locate -0 -b -r '^muc.*_.*_20160920_.*.unl.*gz' | 
   xargs -0  zcat |
   awk -F "|" '$14=="20160920100643" && $22=="567094398953"'| head
  • 1: locate (если доступно) намного быстрее, чем **или find; Используемое регулярное выражение должно быть настроено...

  • 2 и 3: фильтр ПО

Как мудро заметил @rudimeier, существуют проблемы, связанные с доступностью и состоянием обновления locate. (например, на большинстве машин Linux locate обновляется ежедневно; таким образом, он не сможет найти файлы, созданные сегодня)

Тем не менее, если locate доступен, это даст весьма впечатляющее ускорение.

Было бы интересно, если бы ПО мог предоставить time ...различные решения

Связанный контент