バッシュ5.0解決済み

バッシュ5.0解決済み

バッシュ5.0解決済み

背景

背景知識(そしてこの質問に対する反対表を避けるため)のためにこの問題を解決するようになった経路を説明します(2か月後に私が覚えておくための最善の方法です)。

Unicode文字リストに対していくつかのシェルテストを実行するとしましょう。

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

そして、約100万個以上のUnicode文字があり、そのうち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#非常に簡単で、ここにのみ表示されます。
膨大な遅延の原因を見つけるために、元の機能が徐々に削除されています。

上記のスクリプトは機能するため、実行して非常に小さなタスクを実行するのに時間を無駄にすることができます。

待ち時間が正確にどこにあるかを見つけるために単純化する過程で(多くの試みを経た後に各テスト機能をほとんど何もない状態に減らすことは極端です)、時間がどれだけ改善されたかを確認するために各テスト機能に渡すパラメータを削除すると決めました。あまり。

自分で試してみるには、"$@"関数内のすべての項目を削除するか、main1コピーを作成して再テスト(またはその両方とmain1コピーmain2(使用main2 "$@"))して比較してください。これは元の投稿(OP)の基本構造です。

しかし、私は知りたいです。シェルが「何もしない」のになぜそんなに長い時間がかかるのでしょうか?はい、「数秒」しかかかりません。しかし、なぜそうですか?

これは他のシェルでテストし、bashにのみこの問題があることを発見しました。
試してみてくださいksh ./script(上記と同じスクリプト)。

test#これは、パラメータなしで関数を呼び出すと()親関数()のパラメータによって遅延されるという説明につながります。main#以下の説明は次のとおりです。以下のオリジナル投稿(OP)も同様です。

元の投稿。

何もしない関数(Bash 4.4.12(1) - リリースで)を呼び出すのは、呼び出すよりもf1(){ :; }1000倍遅いです。:ただ定義されたパラメータがある場合関数を呼び出す理由は何ですか?

#!/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パラメータリストまたはパラメータリスト処理が遅延の原因ではないことを確認するには、他の2つのテストのコメントを外します。

1それパラメータを介して結果を渡すと、実行時間が長くなることが知られています。。ありがとう@slm

ベストアンサー1

コピーした場所:ループに遅延が発生するのはなぜですか?あなたの要求に従って:

テストケースを次のように短縮できます。

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(現在のアルファ)では悪いと思いましたが、@egmontが指摘したように、開発ビルドでmallocデバッグを有効にするかどうかによって異なります。bashたとえば、Ubuntuでビルドを使用したい場合--without-bash-malloc

おすすめ記事