Вопрос или проблема
У меня есть скрипт, который я использовал много лет для составления списка некоторых каталогов, со следующими строками:
##Найти все сценарии для этого номера сектора
find /gsgt/source/scenarios/AT_* -name ${current}"*" -type d | \
awk -F"/" '{print $NF}' > Sector_${current}_list.txt
find /gsgt/source/scenarios/AT_* -name "3T${current}*" -type d | \
awk -F"/" '{print $NF}' >> Sector_${current}_list.txt
find /gsgt/source/scenarios/AT_* -name "4T${current}*" -type d | \
awk -F"/" '{print $NF}' >> Sector_${current}_list.txt
find /gsgt/source/scenarios/AT_* -name "Sector_${current}*" -type d | \
awk -F"/" '{print $NF}' >> Sector_${current}_list.txt`
Это дает мне список только имен каталогов. Команды выполняются за доли секунды. Недавно мы обнаружили необходимость начинать с полных путей в аналогичном скрипте. Я переписал раздел без передачи в awk следующим образом:
##Найти все сценарии для этого номера сектора, поместить их в список
find /gsgt/source/scenarios/AT_* -name ${current}"*" -type d \
> Sector_${current}_list.txt
find /gsgt/source/scenarios/AT_* -name "3T${current}*" -type d \
>> Sector_${current}_list.txt
find /gsgt/source/scenarios/AT_* -name "4T${current}*" -type d \
>> Sector_${current}_list.txt
find /gsgt/source/scenarios/AT_* -name "Sector_${current}*" -type d \
>> Sector_${current}_list.txt
Это дает мне тот же список с полными путями, и работает, но это занимает почти минуту для завершения. Оно ищет в той же структуре дерева, с теми же входными переменными.
Почему второй вариант, который кажется “проще”, так медленно работает?
Передача результатов в awk позволяет командам обрабатывать результаты в потоке, уменьшая накладные расходы, связанные с хранением и записью полных путей.
Прямые записи в файл без awk означают, что оболочке нужно обрабатывать больше данных и выполнять больше операций чтения/записи на диске, что может быть значительно медленнее, особенно на системах с высокой задержкой диска или множеством маленьких файлов.
Попробуйте это:
find /gsgt/source/scenarios/AT_* \( \
-name "${current}*" -o \
-name "3T${current}*" -o \
-name "4T${current}*" -o \
-name "Sector_${current}*" \
\) -type d > Sector_${current}_list.txt
Что ж, я все еще не знаю точно, почему скрипт работал так медленно, когда я набрал ./myscript.sh (в нем указан shebang для ksh), но запуск его как “ksh ./myscript.sh” сделал все различие. Весь скрипт выполняется за примерно 30 секунд, вместо 8 минут.
Оба скрипта действительно вызывают find 4 раза, но проблема медленной работы второго скрипта не связана только с избыточностью в числе вызовов find.
Разница в производительности между двумя скриптами заключается в том, как обрабатывается вывод и в воспринимаемом влиянии обхода каталогов.
@delirehberi написал однострочник, который отлично справляется с задачей!
Ответ или решение
Сравнение быстродействия команд в Unix-подобных операционных системах показывает, насколько важна оптимизация не только самих команд, но и способа обработки их выводов. В данном случае мы разбираем два варианта выполнения команды find
, и различия в производительности между ними поднимают важные темы, касающиеся управления ресурсами и оптимизации скриптов.
Проблема с производительностью
Ваша первая версия скрипта, использующая конвейер с awk
, выполняется значительно быстрее, чем вторая версия, которая отправляет вывод find
непосредственно в файл. Почему это происходит? Давайте рассмотрим несколько ключевых факторов.
-
Потоковая обработка вывода: Когда вы используете
awk
для обработки выводаfind
, данные передаются в конвейере, что позволяетfind
иawk
работать параллельно.find
находит и передает директории, аawk
сразу же обрабатывает каждую строку по мере ее поступления. Это позволяет сократить время ожидания и уменьшить нагрузку на диск. -
Частота входа-выхода на диск: При выполнении второй версии скрипта, каждый вызов
find
записывает результаты в файл, что требует больше операций ввода-вывода (I/O). Записи на диск являются более медленными по сравнению с обработкой данных в памяти, особенно если файловая система или диск загружены. -
Количество вызовов
find
: Несмотря на то что обе версии вызываютfind
четыре раза, в первом варианте данные обрабатываются и выводятся по мере нахождения, что поэтому улучшает отклик системы. Во втором варианте все данные должны сначала собраться, а затем записаться в файл, что занимает больше времени.
Оптимизация скрипта
Для достижения более эффективного результата, вы можете объединить все ваши условия поиска в один вызов find
с помощью параметра -o
(логическое "ИЛИ"). Например:
find /gsgt/source/scenarios/AT_* \( \
-name "${current}*" -o \
-name "3T${current}*" -o \
-name "4T${current}*" -o \
-name "Sector_${current}*" \
\) -type d > Sector_${current}_list.txt
Таким образом, вы снизите количество операций ввода-вывода и улучшите общую производительность скрипта.
Почему проблема исчезла при запуске через ksh
?
Феномен, наблюдаемый при запуске вашего скрипта с использованием ksh ./myscript.sh
, указывает на различия в интерпретаторах командной строки. Возможно, при запуске ./myscript.sh
использовался другой интерпретатор (например, sh
), который может иметь различные настройки производительности и оптимизации. Эти настройки могли повлиять на то, как выполнялись команды в скрипте. Различия в реализации этих интерпретаторов ведут к вот таким значительным изменениям во времени выполнения. Важно всегда проверять, какой интерпретатор используется для выполнения скриптов, если производительность важна.
Заключение
Работа с командами на Unix требует внимательного подхода к оптимизации не только самих команд, но и их взаимодействия друг с другом. Использование потоков обработки и сокращение количества обращений к файловой системе могут существенно сократить время выполнения. С учетом этих рекомендаций, вы сможете значительно повысить эффективность ваших скриптов и добиться желаемых результатов быстрее и надежнее.