bash 5.0で解決済み
バックグラウンド
背景(および理解(およびこの質問への反対票を避けようとすることは魅力的であるように思われます))のために、この問題にたどり着いた経路を説明します(まあ、2か月後に思い出せる最善の方法です)。
Unicode文字のリストに対していくつかのシェルテストを実行していると仮定します。
printf "$(printf '\\U%x ' {33..200})"
また、100万を超える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、それほど多くありません。
自分で試すには、すべての"$@"
in関数を削除してmain1
(またはコピーを作成して)、もう一度テストして(または両方main1
とコピーmain2
(を使用してmain2 "$@"
))比較します。これは、元の投稿(OP)の下の基本構造です。
しかし、私は疑問に思った:なぜシェルは「何もしない」のにそんなに時間がかかるのか?はい、「数秒」だけですが、それでも、なぜですか。
これにより、他のシェルでテストして、bashのみがこの問題を抱えていることを発見しました。
試してくださいksh ./script
(上記と同じスクリプト)。
これはこの説明につながります:test#
引数なしで関数()を呼び出すと、親(main#
)の引数によって遅延します。これは以下の説明であり、以下の元の投稿(OP)でした。
元の投稿。
何もしないように(バッシュ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
function f1
で使用される引数も入力または出力もありません。1000分の1の遅延は予期されていません。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 ......
他の2つのテストのコメントを外し、どちらもseq
引数リストを処理していないことが遅延の原因であることを確認します。
1結果を引数で渡すと実行時間が長くなることが知られています。ありがとう@slm