Решено в bash 5.0
Фон
Для справки (и понимания (и попыток избежать отрицательных голосов этот вопрос, кажется, привлекает)) я объясню путь, который привел меня к этой проблеме (ну, лучшее, что я могу вспомнить через два месяца).
Предположим, вы выполняете некоторые тесты оболочки для списка символов Unicode:
printf "$(printf '\\U%x ' {33..200})"
и, учитывая, что в Unicode содержится более 1 миллиона символов, тестирование 20 000 из них выглядит не так уж и много.
Также предположим, что вы устанавливаете символы в качестве позиционных аргументов:
set -- $(printf "$(printf '\\U%x ' {33..20000})")
с целью передачи символов каждой функции, чтобы обрабатывать их по-разному. Таким образом, функции должны иметь форму test1 "$@"
или подобное. Теперь я понимаю, как это плохо в bash.
Теперь предположим, что существует необходимость в времени (n = 1000) каждого решения, чтобы выяснить, что лучше, в таких условиях вы получите структуру, похожую на:
#!/bin/bash --
TIMEFORMAT='real: %R' # '%R %U %S'
set -- $(printf "$(printf '\\U%x ' {33..20000})")
n=1000
test1(){ echo "$1"; } >/dev/null
test2(){ echo "$#"; } >/dev/null
test3(){ :; }
main1(){ time for i in $(seq $n); do test1 "$@"; done
time for i in $(seq $n); do test2 "$@"; done
time for i in $(seq $n); do test3 "$@"; done
}
main1 "$@"
Функции test#
сделаны очень очень просто, чтобы быть представленными здесь.
Оригиналы были постепенно урезаны, чтобы найти, где была огромная задержка.
Сценарий выше работает, вы можете запустить его и тратить несколько секунд, занимаясь совсем немного.
В процессе упрощения, чтобы точно определить, где была задержка (а сокращение многих тестовых функций почти до нуля является предельным после многих испытаний), я решил убрать передачу аргументов каждой тестовой функции, чтобы выяснить, насколько улучшилось время, только в 6 раз, не очень.
Чтобы попробовать себя, удалите все "$@"
функции in main1
(или сделайте копию) и протестируйте снова (или оба, main1
и копию main2
(с main2 "$@"
)) для сравнения. Это базовая структура ниже в исходном посте (ОП).
Но я задавался вопросом: почему оболочка так долго "ничего не делает"? Да только «пару секунд», но все же, зачем?
Это заставило меня проверить другие оболочки, чтобы обнаружить, что эта проблема была только у bash.
Попробуйте ksh ./script
(тот же сценарий, что и выше).
Это приводит к этому описанию: вызов функции ( test#
) без каких-либо аргументов задерживается аргументами в parent ( main#
). Это описание, которое следует и было оригинальным постом (OP) ниже.
Оригинальный пост.
Вызов функции (в Bash 4.4.12 (1) -release), который ничего не делает f1(){ :; }
, в тысячу раз медленнее, :
но только если в родительской вызывающей функции определены аргументы. Почему?
#!/bin/bash
TIMEFORMAT='real: %R'
f1 () { :; }
f2 () {
echo " args = $#";
printf '1 function no args yes '; time for ((i=1;i<$n;i++)); do : ; done
printf '2 function yes args yes '; time for ((i=1;i<$n;i++)); do f1 ; done
set --
printf '3 function yes args no '; time for ((i=1;i<$n;i++)); do f1 ; done
echo
}
main1() { set -- $(seq $m)
f2 ""
f2 "$@"
}
n=1000; m=20000; main1
Результаты test1
:
args = 1
1 function no args yes real: 0.013
2 function yes args yes real: 0.024
3 function yes args no real: 0.020
args = 20000
1 function no args yes real: 0.010
2 function yes args yes real: 20.326
3 function yes args no real: 0.019
В функции нет ни аргументов, ни входных, ни выходных данных f1
, задержка в тысячу (1000) раз неожиданна. 1
Если результаты тестов распространяются на несколько оболочек, результаты совпадают, большинство оболочек не испытывают никаких проблем и не страдают от задержек (используются те же n и m):
test2(){
for sh in dash mksh ksh zsh bash b50sh
do
echo "$sh" >&2
# \time -f '\t%E' seq "$m" >/dev/null
# \time -f '\t%E' "$sh" -c 'set -- $(seq '"$m"'); for i do :; done'
\time -f '\t%E' "$sh" -c 'f(){ :;}; while [ "$((i+=1))" -lt '"$n"' ]; do : ; done;' $(seq $m)
\time -f '\t%E' "$sh" -c 'f(){ :;}; while [ "$((i+=1))" -lt '"$n"' ]; do f ; done;' $(seq $m)
done
}
test2
Результаты:
dash
0:00.01
0:00.01
mksh
0:00.01
0:00.02
ksh
0:00.01
0:00.02
zsh
0:00.02
0:00.04
bash
0:10.71
0:30.03
b55sh # --without-bash-malloc
0:00.04
0:17.11
b56sh # RELSTATUS=release
0:00.03
0:15.47
b50sh # Debug enabled (RELSTATUS=alpha)
0:04.62
xxxxxxx More than a day ......
Раскомментируйте два других теста, чтобы подтвердить, что ни seq
задержка , ни обработка списка аргументов не являются источником.
1 Этоизвестночто передача результатов аргументов увеличивает время выполнения. Спасибо@slm
Ответы:
Скопировано из: Почему задержка в цикле? по вашему запросу:
Вы можете сократить контрольный пример до:
Он вызывает функцию, когда
$@
она велика, что, кажется, вызывает ее.Я предполагаю, что время тратится на сохранение
$@
в стеке и его восстановление впоследствии. Возможно,bash
делает это очень неэффективно, дублируя все значения или что-то в этом роде. Кажется, время в o (n²).Вы получаете такое же время в других оболочках для:
Именно здесь вы передаете список аргументов функциям, и на этот раз оболочка должна скопировать значения (в
bash
конечном итоге это в 5 раз медленнее).(Сначала я думал, что это хуже в bash 5 (в настоящее время в альфа-версии), но это сводилось к тому, что отладка malloc была включена в версиях разработки, как отмечает @egmont; также проверьте, как собирается ваш дистрибутив,
bash
если вы хотите сравнить свою собственную сборку с система одна. Например, Ubuntu использует--without-bash-malloc
)источник
RELSTATUS=alpha
кRELSTATUS=release
вconfigure
сценарии.--without-bash-malloc
иRELSTATUS=release
к результатам вопроса. Это все еще показывает проблему с вызовом ф.:
и немного улучшает вызовf
. Посмотрите на сроки test2 в вопросе.