Bashは引数リストを使用してパフォーマンスに問題がありますか?


11

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


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(現在はアルファ版)でより悪いと思っていましたが、@ egmontで述べられているように、開発バージョンでmallocデバッグが有効になっていることが原因でした。またbash、独自のビルドと、システムのものです。たとえば、Ubuntuは--without-bash-malloc)を使用します


デバッグはどのように削除されますか?
NotAnUnixNazi 2018

@isaac、私はスクリプトでに変更RELSTATUS=alphaすることでそれを行いRELSTATUS=releaseましたconfigure
ステファンChazelas

--without-bash-mallocおよび両方のテスト結果をRELSTATUS=release質問結果に追加しました。それでも、fの呼び出しに問題があります。
NotAnUnixNazi 2018

@アイザック、はい、私は以前bash5の方が悪いと言っていたのは間違っていたと言っただけです。悪くはありませんが、同じくらい悪いです。
ステファンChazelas

いいえ、それほど悪くはありません。Bash5は呼び出し:に関する問題を解決し、呼び出しを少し改善しfます。質問のtest2のタイミングを見てください。
-NotAnUnixNazi
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.