У Bash виникають проблеми з використанням аргументів списків?


11

Вирішено в базі 5,0

Фон

Для передумови (і розуміння (і намагаючись уникнути низових подій це питання, схоже, приваблює)) я поясню шлях, який пройшов мене до цього питання (ну найкраще, що я можу згадати через два місяці).

Припустимо, ви робите кілька тестів на оболонку для списку символів Unicode:

printf "$(printf '\\U%x ' {33..200})"

і там є більше ніж 1 мільйон символів Unicode, тестування 20 000 з них, здається, не так вже й багато.
Також припустимо, що ви задаєте символи як позиційні аргументи:

set -- $(printf "$(printf '\\U%x ' {33..20000})")

з наміром передати персонажів до кожної функції, щоб обробити їх різними способами. Отже функції повинні мати форму test1 "$@"або подібну. Тепер я розумію, наскільки це погана ідея в баші.

Тепер, припустимо, що для вирішення питання, який є кращим, потрібно вчасно (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, не багато.

Щоб спробувати себе, видаліть усі "$@"функції ввімкнення main1(або зробіть копію) і повторіть тест (або обидва, main1і копію main2main2 "$@")) для порівняння. Це основна структура нижче в початковому дописі (ОП).

Але я задумався: чому оболонці потрібно так довго, щоб "нічого не робити" ?. Так, лише «пару секунд», але все ж, чому ?.

Це змусило мене випробувати в інших снарядах, щоб виявити, що тільки баш мав цю проблему.
Спробуйте ksh ./script(той самий сценарій, що і вище).

Це призводить до цього опису: виклик функції ( test#) без жодних аргументів затримується аргументами в батьківському ( main#). Це опис, який випливає, і був початковим повідомленням (OP) нижче.

Оригінальна публікація.

Виклик функції (в Bash 4.4.12 (1) -випуск) робити нічого 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


3
Збережено мета-ефектом. unix.meta.stackexchange.com/q/5021/3562
Джошуа

Відповіді:


9

Скопійовано з: Чому затримка циклу? на ваше прохання:

Ви можете скоротити тестовий випадок до:

time bash -c 'f(){ :;};for i do f; done' {0..10000}

Це викликає функцію, поки $@вона велика, що, здається, запускає її.

Я гадаю, що час витрачається економлячи $@на стек і відновлюючи його згодом. Можливо bash, це робить дуже неефективно, дублюючи всі цінності чи щось подібне. Час, здається, знаходиться в o (n²).

Ви отримуєте такий же час в інших оболонках для:

time zsh -c 'f(){ :;};for i do f "$@"; done' {0..10000}

Ось де ви передаєте список аргументів функціям, і цього разу оболонці потрібно скопіювати значення (у bashкінцевому підсумку це у 5 разів повільніше для цього).

(Я спочатку думав, що це гірше в bash 5 (зараз в альфа), але це було до того, що налагодження на malloc включено у версіях розробки, як зазначає @egmont; також перевірити, як будується ваш дистрибутив, bashякщо ви хочете порівняти власну збірку з система одна. Наприклад, Ubuntu використовує --without-bash-malloc)


Як видаляється налагодження?
Ісаак

@isaac, я зробив це, змінивши RELSTATUS=alphaдо RELSTATUS=releaseв configureсценарії.
Стефан Шазелас

Додано результати тестування як для результатів, так --without-bash-mallocі RELSTATUS=releaseдля результатів. Це все ще свідчить про проблему із закликом до f.
Ісаак

@Isaac, так, я просто сказав, що я неправильно говорив, що в bash5 гірше. Це не гірше, так само погано.
Стефан Шазелас

Ні, це не так вже й погано . Bash5 вирішує проблему з дзвінками :та трохи покращує виклики f. Подивіться терміни test2 у питанні.
Ісаак
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.