Как определить самый медленный компонент моего конвейера оболочки?

У меня очень длинный и сложный конвейер оболочки, настроенный на захват 2,2 ГБ данных и их обработку. В настоящее время обработка занимает 45 минут. Конвейер представляет собой набор команд cut, grep, sort, uniq, grep и awk, связанных вместе. У меня есть подозрение, что это часть grep, из-за которой это занимает так много времени, но у меня нет возможности это подтвердить.

Можно ли как-то «профилировать» весь конвейер от начала до конца, чтобы определить, какой компонент является самым медленным, и связан ли он с процессором или вводом-выводом, чтобы его можно было оптимизировать?

К сожалению, я не могу опубликовать здесь всю команду, так как это потребует публикации конфиденциальной информации, но я подозреваю, что это следующий бит, проверяющий ее с помощью htop:

grep -v ^[0-9]

person Deleted    schedule 10.10.2011    source источник
comment
Sort должен прочитать весь свой ввод для сортировки, я подозреваю, что это проблема. Если бы у меня было столько данных и сложный конвейер, я бы вместо этого переписал его на Perl.   -  person Christoffer Hammarström    schedule 10.10.2011
comment
Sort находится после уникальной части конвейера и должен отсортировать только около 1500 элементов, поэтому я не думаю, что это так.   -  person Deleted    schedule 10.10.2011
comment
Вы знаете, что uniq ожидает отсортированный ввод?   -  person Christoffer Hammarström    schedule 10.10.2011
comment
Это не требует отсортированного ввода (я подтвердил это), но оптимально, если вы сначала отсортируете его. В этом случае меня это не беспокоит, так как количество строк до uniq составляет около 15000, что занимает меньше секунды, чтобы разорвать этот ящик. По поводу перла - рассматриваю.   -  person Deleted    schedule 10.10.2011
comment
Хорошо, просто чтобы удовлетворить мое любопытство, что это за реализация uniq? Я не слышал ни об одном, который не требует сортированного ввода. В любом случае, этот grep не должен быть проблемой, поэтому я предполагаю, что он больше связан с вводом-выводом.   -  person Christoffer Hammarström    schedule 10.10.2011
comment
Уникальный Coreutils. Я могу ошибаться в своем первоначальном предположении. Теперь я нашел проблему - это был grep. Опубликую свой собственный ответ ниже.   -  person Deleted    schedule 10.10.2011
comment
Я обнаружил, что ввод не нужно сортировать, но повторяющиеся строки ввода обнаруживаются только в том случае, если они являются смежными. поэтому я обеспокоен тем, что у вас могут быть повторяющиеся строки, которые не повторяются. sort -u должен позаботиться об этом.   -  person Christoffer Hammarström    schedule 10.10.2011
comment
Спасибо за внимание - я соответственно обновил свои сценарии.   -  person Deleted    schedule 10.10.2011


Ответы (3)


Один из способов сделать это — постепенно наращивать конвейер, синхронизируя каждое добавление и убирая как можно больше из уравнения (например, вывод на терминал или в файл). Очень простой пример показан ниже:

pax:~$ time ( cat bigfile >/dev/null )
real 0m4.364s
user 0m0.004s
sys  0m0.300s

pax:~$ time ( cat bigfile | tr 'a' 'b' >/dev/null )
real 0m0.446s
user 0m0.312s
sys  0m0.428s

pax:~$ time ( cat bigfile | tr 'a' 'b' | tail -1000l >/dev/null )
real 0m0.796s
user 0m0.516s
sys  0m0.688s

pax:~$ time ( cat bigfile | tr 'a' 'b' | tail -1000l | sort -u >/dev/null )
real 0m0.892s
user 0m0.556s
sys  0m0.756s

Если вы суммируете пользовательское и системное время выше, вы увидите, что постепенное увеличение составляет:

  • 0,304 (0,004 + 0,300) секунды для cat;
  • 0,436 (0,312 + 0,428 - 0,304) секунды для tr;
  • 0,464 (0,516 + 0,688 - 0,436 - 0,304) секунды для tail; и
  • 0,108 (0,556 + 0,756 - 0,464 - 0,436 - 0,304) секунды для sort.

Это говорит мне о том, что главное, на что нужно обратить внимание, это tail и tr.

Теперь очевидно, что это только для ЦП, и мне, вероятно, следовало бы выполнить несколько прогонов на каждом этапе для целей усреднения, но это основной первый подход, который я бы выбрал.

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

state = echo
lastchar = newline
while not end of file:
    read big chunk from file
    for every char in chunk:
        if lastchar is newline:
            if state is echo and char is non-digit:
                state = skip
            else if state is skip and and char is digit:
                state = echo
        if state is echo:
            output char
        lastchar = char

Пользовательский целевой код, подобный этому, иногда можно сделать более эффективным, чем механизм обработки регулярных выражений общего назначения, просто потому, что его можно оптимизировать для конкретного случая. Вы должны проверить, верно ли это в данном случае или в любом случае. Моя главная мантра оптимизации: измеряй, а не гадай!

person paxdiablo    schedule 10.10.2011
comment
Спасибо - это действительно элегантный подход к анализу. Очень признателен. Я отмечу это как ответ на том основании, что это правильный подход к проблеме. Еще раз спасибо. - person Deleted; 10.10.2011

Я нашел проблему сам после некоторых дальнейших экспериментов. Похоже, это связано с поддержкой кодировки в grep. С помощью следующего зависшего конвейера:

grep -v ^[0-9]

Я заменил его на sed следующим образом, и он закончился менее чем за 45 секунд!

sed '/^[0-9]/d'
person Deleted    schedule 10.10.2011

Это просто с zsh:

zsh-4.3.12[sysadmin]% time sleep 3 | sleep 5 | sleep 2
sleep 3  0.01s user 0.03s system 1% cpu 3.182 total
sleep 5  0.01s user 0.01s system 0% cpu 5.105 total
sleep 2  0.00s user 0.05s system 2% cpu 2.121 total
person Dimitre Radoulov    schedule 22.10.2011