benchmark.txt (12784B)
1 注意: ここに載せるのはループで回した時の時間である。parse 等の時間は含まれないと考えるべき。 2 注意: 何故か評価の順序によって時間が変わったりするので何度か試すのが良い。 3 4 * commands 5 6 which -> type (関数 エイリアス 組込コマンドに注意) 7 date -> printf %()T 8 expr, bc -> 算術式 9 正規表現 -> [[ ... =~ ... ]] 10 enable (builtin を自分で作る) 11 cat, $(< ...) -> read 12 13 組込コマンドで実装する: 14 wc -l, tac, tee, cat, paste, ... 15 od, base64, wc -c, uuencode (ascii85) 16 17 裏機能 18 プロセス存在確認 -> kill -0 19 ポーリング -> read -t 0 20 21 * test 22 23 [ よりは test, test よりは [[。 24 25 [[ a ]] 26 time 2.00 usec/eval 27 test a 28 time 5.40 usec/eval 29 [ a ] 30 time 6.10 usec/eval 31 32 "[ よりは test" というのは今迄の経験と一致する。 33 "test よりは [[" というのは当たり前の事である。 34 これに関しては単語分割が速度を支配している気がする。 35 [ は単語が余分に一個多い。[[ は毎回の実行時に単語分割はしない。 36 37 変数名の quote はしない。 38 39 [[ $dir == / ]] 40 time 5.00 usec/eval 41 [[ "$dir" == / ]] 42 time 7.40 usec/eval 43 44 変数名は長くても短くても殆ど違いはない。でも、微妙に短い方が良い? 45 46 [[ $d == / ]] 47 time 5.00 usec/eval 48 [[ $dir == / ]] 49 time 5.10 usec/eval 50 [[ $dirverylonglong == / ]] 51 time 5.50 usec/eval 52 53 右辺の glob パターンは特別な文字が含まれていなくても下手に quote しない方が速い。 54 55 [[ $dir == / ]] 56 time 5.10 usec/eval 57 [[ $dir == '/' ]] 58 time 5.90 usec/eval 59 [[ $dir == "/" ]] 60 time 6.90 usec/eval 61 62 startsWith 63 64 glob による一致が最も速い。というか単純な == / の比較と大して変わらない速度。 65 パラメータ展開を用いるのは多少時間が掛かる。新しい文字列インスタンスを作るからか。 66 正規表現を用いる方法はさすがに遅い。しかし思った程には悪くないようだ。 67 68 [[ $d == /* ]] 69 time 5.10 usec/eval 70 [[ ${d::1} == / ]] 71 time 10.30 usec/eval 72 [[ ! ${d##/*} ]] 73 time 10.50 usec/eval 74 [[ $d =~ ^/ ]] 75 time 26.40 usec/eval 76 77 contains 78 79 同じ傾向だ。 80 81 [[ $d == */* ]] 82 time 5.60 usec/eval 83 [[ ! ${d##*/*} ]] 84 time 11.00 usec/eval 85 [[ $d =~ / ]] 86 time 20.20 usec/eval 87 88 89 * 代入 90 91 右辺の quote はない方が良い。 92 93 変数の代入の右辺は単語分割の対象ではないので、quote する必要はない。 94 (quote の必要があるのはチルダ展開ぐらいだろうか。) 95 これは declare a=$b の形式でも同様である。 96 97 d=$d 98 time 3.50 usec/eval 99 d="$d" 100 time 6.20 usec/eval 101 102 複数の変数の代入はまとめてした方が速い 103 104 a=$d b=$d 105 time 6.70 usec/eval 106 a=$d; b=$d 107 time 9.40 usec/eval 108 109 * 算術式 110 111 代入は直接文字列として実行した方が速い。 112 113 a=0 114 time 1.40 usec/eval 115 ((a=0)) 116 time 3.80 usec/eval 117 118 a=0 b=0 119 time 2.70 usec/eval 120 ((a=0,b=0)) 121 time 5.80 usec/eval 122 123 呼出は (( )) が一番速い。 124 125 やはり単純な (( )) が一番速い。 126 意外にも let は変数の代入を用いる方法よりも遅い。やはり単語分割が遅い説? 127 128 ((a++)) 129 time 4.20 usec/eval 130 (('a++')) 131 time 4.70 usec/eval 132 a=$((a+1)) 133 time 7.20 usec/eval 134 let a++ 135 time 7.70 usec/eval 136 let 'a++' 137 time 8.50 usec/eval 138 139 条件判定 140 141 表現が正規化されている事が前提で、かつ等値判定 (== !=) ならば文字列としての比較が最速だ。 142 それ以外の場合 (変数内に数式があるかもしれない or 不等判定(< > <= =>)) は算術式を使うのが良い。 143 144 [[ a == 0 ]] && x=1 145 time 5.30 usec/eval 146 ((a==0)) && x=1 147 time 8.40 usec/eval 148 [[ a -eq 0 ]] && x=1 149 time 11.10 usec/eval 150 151 条件分岐も算術式の内部でした方が速い 152 153 算術式 && vs コマンド && vs if (真) 154 155 ((a==0&&(x=1))) 156 time 6.50 usec/eval 157 ((a==0)) && x=1 158 time 8.20 usec/eval 159 ((a==0)) && ((x=1)) 160 time 10.30 usec/eval 161 if ((a==0)); then ((x=1)); fi 162 time 10.40 usec/eval 163 164 算術式 && vs コマンド && vs if (偽) 165 166 →内部の式が評価されない場合には "コマンド &&" の方が速い様だ。 167 168 ((a==1&&(x=1))) 169 time 5.70 usec/eval 170 ((a==1)) && ((x=1)) 171 time 4.60 usec/eval 172 if ((a==1)); then ((x=1)); fi 173 time 4.90 usec/eval 174 175 三項条件式 vs if-else (真) 176 177 ((a==0?(x=1):(y=1))) 178 time 9.00 usec/eval 179 if ((a==0)); then ((x=1)); else ((y=1)); fi 180 time 11.00 usec/eval 181 182 三項条件式 vs if-else (偽) 183 184 ((a==1?(x=1):(y=1))) 185 time 8.80 usec/eval 186 if ((a==1)); then ((x=1)); else ((y=1)); fi 187 time 10.70 usec/eval 188 189 複雑な条件分岐の場合 190 →条件を多少複雑にしても基本的に算術式が速い様である。 191 192 ((x=a==1?1:(a==2?8:(a==3?4:3)))) 193 time 12.30 usec/eval 194 ((a==1?(x=1):(a==2?(x=8):(a==3?(x=4):(x=3))))) 195 time 18.30 usec/eval 196 if [[ a == 1 ]]; then x=1; elif [[ a == 2 ]]; then x=8; elif [[ a == 3 ]]; then x=4; else x=3; fi 197 time 21.80 usec/eval 198 if ((a==1)); then ((x=1)); elif ((a==2)); then ((x=8)); elif ((a==3)); then ((x=4)); else ((x=3)); fi 199 time 24.10 usec/eval 200 201 * 関数呼出 202 203 関数呼出はそんなに遅くないという感覚でいたがまあ確かにそんなに遅くない。 204 しかしそうは言っても完全に無視できる速さという訳ではない。 205 が代替の場合は関数にしてもそんなに問題はない・効果はないだろう。 206 一応関数名が短い方が多少速い。 207 208 function _empty { :; } 209 function very_very_long_long_function_name { :; } 210 211 : 212 time 3.30 usec/eval 213 _empty 214 time 14.20 usec/eval 215 very_very_long_long_function_name 216 time 18.40 usec/eval 217 218 * ループ 219 220 1 ずつ増える変数についての固定長のループならば、 221 ループのサイズに関係なくブレース展開の方が微妙に速い。 222 但しブレース展開は以下の制限がある。 223 + 上限と下限が変数の場合は使えない → ループが十分大きければ eval にすれば OK. 224 + set +o braceexpand (+B) としている時には利用できない. 225 226 a=0; for i in {0..10}; do ((a+=i)); done 227 time 132.80 usec/eval 228 a=0; for ((i=0;i<10;i++)); do ((a+=i)); done 229 time 187.80 usec/eval 230 a=0; for i in {0..10000}; do ((a+=i)); done 231 time 131972.70 usec/eval 232 a=0; for ((i=0;i<10000;i++)); do ((a+=i)); done 233 time 185972.70 usec/eval 234 235 ------------------------------------------------------------------------------ 236 配列操作 237 ------------------------------------------------------------------------------ 238 239 push 240 241 j=0; for i in {0..1000}; do a[j++]=$i; done 242 time 15772.50 usec/eval 243 for i in {0..1000}; do a+=($i); done 244 time 16872.50 usec/eval 245 for i in {0..1000}; do a[${#a[@]}]=$i; done 246 time 19172.50 usec/eval 247 for i in {0..1000}; do a=("${a[@]}" $i); done 248 time 2470972.50 usec/eval 249 250 巨大配列と他の配列を混ぜてループすると滅茶苦茶遅い O(N^2) 251 252 A a=(); ble-measure "for ((i=0;i<$n;i++)); do ((a[i]=i*i,b[i]=i)); done" 253 B a=({0..1000000}); ble-measure "for ((i=0;i<$n;i++)); do ((a[i]=i*i,b[i]=i)); done" 254 C a=({0..1000000}); ble-measure "for ((i=0;i<$n;i++)); do ((a[i]=i*i)); done; for ((i=0;i<$n;i++)); do ((b[i]=i)); done" 255 256 usec/eval A usec/eval B usec/eval C 257 n=10 300.20 302.00 453.90 258 n=20 598.20 595.00 889.90 259 n=50 1522.20 1522.00 2211.90 260 n=100 3072.20 3082.00 4471.90 261 n=200 6252.20 6222.00 8991.90 262 n=500 16772.20 16472.00 22271.90 263 n=1000 41072.20 38572.00 49571.90 264 n=2000 95472.20 89472.00 102171.90 265 n=5000 159972.20 240972.20 239971.90 266 n=10000 315972.20 631972.20 485971.90 267 n=20000 638972.20 2052972.00 971971.90 268 n=50000 1599972.20 13345972.20 2435972.20 269 n=100000 3231972.20 67832972.20 4969972.20 270 n=200000 6508972.20 9975972.20 271 n=500000 25708972.00 272 n=1000000 53124972.00 273 274 予想: 275 bash は配列を双方向リストで実装している。 276 末尾または先頭へのアクセスは高速にできる。 277 シーケンシャルなアクセスも高速にできる様に、前回アクセスした配列と位置を記録している。 278 他の配列に触ると前回アクセスした位置は失われる。 279 280 別の変数に触る場合は遅くならない。 281 282 x=0; for ((i=0;i<200000;i++)); do ((a[i]=i*i,x=i)); done 283 time 5647972.00 usec/eval 284 285 100k 21.978s 4 286 200k 33.204s 16 287 300k 50.981s 34 288 1000k 6m56.349s 289 290 291 globpat による配列要素のフィルタ 292 293 配列要素一つずつに対して [[ ]] で検索するのが素直な実装である。 294 一方で compgen にも同様の機能がある。 295 配列要素数が少ない内は compgen を用いた方が速いが、 296 配列要素が多くなってくると compgen を用いた方法は遅くなっていく。 297 (compgen 自体が悪いのか単語分割が遅いのかは分からない)。 298 299 | a=({0..99999}) 300 | function filter1 { i=0 b=(); for x in "${a[@]}"; do [[ $x == *1?2 ]] && b[i++]=$x; done; } 301 | function filter2 { veval b 'compgen -X "!*1?2" -W "${a[*]}"'; b=($b); } 302 | ble-measure 'filter1' 303 | ble-measure 'filter2' 304 | function filter2base { veval b 'type filter2base'; } 305 306 a length filter1 filter2 (usec/eval) 307 -------- ---------- ---------- 308 1000 13171.50 4721.50 309 10000 139971.50 79671.50 310 100000 1529971.30 4670971.40 311 312 filter2base ... 343.50 usec 313 314 ------------------------------------------------------------------------------ 315 コマンド起動 316 ------------------------------------------------------------------------------ 317 318 * fork test 319 320 http://qiita.com/b4b4r07/items/726df1543fc48d2cb20b 321 322 printf -v A '%d + ' {1..10000}; echo $((${A}0)) 323 time 40071.90 usec/eval 324 n=0; for i in {1..10000}; do ((n+=i)); done; echo $n 325 time 134971.90 usec/eval 326 for i in $(seq 10000); do printf "$i + "; [ $i -eq 10000 ] && printf "0\n"; done | bc 327 time 408971.90 usec/eval 328 n=0; for i in $(seq 10000); do n=$(echo $n + $i | bc); done; echo $n 329 time 29894971.90 usec/eval 330 331 * ... vs builtin ... 332 333 builtin を先頭につければパスを探索する必要がないと思ったが別に速くはならない。 334 というか良く考えたら builtin を検索するのにも同じだけ時間が掛かる。 335 336 337 * 間接代入 (ある変数に代入先の変数名が入っている場合) 338 339 こういう奴です: eval "$var=\"\$value\"" 340 341 (($var=value)) ※値が整数値の場合限定 342 time 5.50 usec/eval 343 printf -v "$var" %s "$value" 344 time 16.00 usec/eval 345 eval "$var=\"\$value\"" 346 time 31.30 usec/eval 347 IFS= read -r -d '' "$var" <<< "$value" 348 time 111.60 usec/eval 349 350 * コマンド置換 351 352 ------------------------------------------------------------------------------ 353 Bash 状態 354 ------------------------------------------------------------------------------ 355 356 * shopt -q optname 357 358 shopt -s/-u optname による設定の確認。 359 shopt -q はリダイレクトを伴うからか遅い。というか >/dev/null は不要っぽい。 360 また使いやすい様に関数を定義してみたりしたが関数呼出はやはり遅くなる様だ。 361 362 shopt -q extquote 363 time 8.40 usec/eval 364 [[ :$BASHOPTS: == *:extquote:* ]] 365 time 11.40 usec/eval 366 function ble/util/has-shopt { [[ :$BASHOPTS: == *:"$1":* ]]; } 367 ble/util/has-shopt extquote 368 time 29.00 usec/eval 369 shopt -q extquote &>/dev/null 370 time 32.40 usec/eval 371 372 * [[ -o option-name ]] 373 374 set -o/+o ... による設定の確認。 375 基本的には [[ -o name ]] で確認することが出来る (bash-3.0 以降確認済, 他未確認)。 376 一部の option-name に関してはシェル変数 $- を用いて確認する事も出来る。 377 しかし [[ -o name ]] を使うのが速い様である。 378 379 [[ -o hashall ]] 380 time 3.60 usec/eval 381 [[ $- == *h* ]] 382 time 5.60 usec/eval 383 [[ :$SHELLOPTS: == *:hashall:* ]] 384 time 8.90 usec/eval