benchmark.sh (14731B)
1 #!/bin/bash 2 3 #%define 1 4 #%if target == "ksh" 5 #%% $ echo _ble_measure_target=ksh 6 #%end 7 if ! type ble/util/print &>/dev/null; then 8 function ble/util/unlocal { builtin unset -v "$@"; } 9 function ble/util/print { builtin printf '%s\n' "$1"; } 10 function ble/util/print-lines { builtin printf '%s\n' "$@"; } 11 fi 12 13 function ble-measure/.loop { 14 # Note: ksh requires to quote ; 15 builtin eval "function _target { ${2:+"$2; "}return 0; }" 16 local __ble_i __ble_n=$1 17 for ((__ble_i=0;__ble_i<__ble_n;__ble_i++)); do 18 _target 19 done 20 } 21 22 ## @fn ble-measure/.time n command 23 ## @param[in] n command 24 ## @var[out] ret 25 ## 計測にかかった総時間を μs 単位で返します。 26 if ((BASH_VERSINFO[0]>=5)) || 27 { [[ ${ZSH_VERSION-} ]] && zmodload zsh/datetime &>/dev/null && [[ ${EPOCHREALTIME-} ]]; } || 28 [[ ${SECONDS-} == *.??? ]] 29 then 30 ## @fn ble-measure/.get-realtime 31 ## @var[out] ret 32 if [[ ${EPOCHREALTIME-} ]]; then 33 _ble_measure_resolution=1 # [usec] 34 function ble-measure/.get-realtime { 35 local LC_ALL= LC_NUMERIC=C 36 ret=$EPOCHREALTIME 37 } 38 else 39 # Note: ksh does not have "local"-equivalent for the POSIX-style functions, 40 # so we do not set the locale here. Anyway, we do not care the 41 # interference with outer-scope variables since this script is used 42 # limitedly in ksh. 43 _ble_measure_resolution=1000 # [usec] 44 function ble-measure/.get-realtime { 45 ret=$SECONDS 46 } 47 fi 48 function ble-measure/.time { 49 ble-measure/.get-realtime 2>/dev/null; local __ble_time1=$ret 50 ble-measure/.loop "$1" "$2" &>/dev/null 51 ble-measure/.get-realtime 2>/dev/null; local __ble_time2=$ret 52 53 # convert __ble_time1 and __ble_time2 to usec 54 # Note: ksh does not support empty index as ${__ble_frac::6}. 55 local __ble_frac 56 [[ $__ble_time1 == *.* ]] || __ble_time1=${__ble_time1}. 57 __ble_frac=${__ble_time1##*.}000000 __ble_time1=${__ble_time1%%.*}${__ble_frac:0:6} 58 [[ $__ble_time2 == *.* ]] || __ble_time2=${__ble_time2}. 59 __ble_frac=${__ble_time2##*.}000000 __ble_time2=${__ble_time2%%.*}${__ble_frac:0:6} 60 61 ((ret=__ble_time2-__ble_time1)) 62 ((ret==0&&(ret=_ble_measure_resolution))) 63 ((ret>0)) 64 } 65 elif [[ ${ZSH_VERSION-} ]]; then 66 _ble_measure_resolution=1000 # [usec] 67 #%if target == "ksh" 68 # [ksh incompatible code stripped] 69 #%else 70 function ble-measure/.time { 71 local result= 72 result=$({ time ( ble-measure/.loop "$1" "$2" ; ) } 2>&1 ) 73 #local result=$({ time ( ble-measure/.loop "$1" "$2" &>/dev/null); } 2>&1) 74 result=${result##*cpu } 75 local rex='(([0-9]+):)?([0-9]+)\.([0-9]+) total$' 76 if [[ $result =~ $rex ]]; then 77 if [[ -o KSH_ARRAYS ]]; then 78 local m=${match[1]} s=${match[2]} ms=${match[3]} 79 else 80 local m=${match[2]} s=${match[3]} ms=${match[4]} 81 fi 82 m=${m:-0} ms=${ms}000; ms=${ms:0:3} 83 84 ((ret=((10#0$m*60+10#0$s)*1000+10#0$ms)*1000)) 85 return 0 86 else 87 builtin echo "ble-measure: failed to read the result of \`time': $result." >&2 88 ret=0 89 return 1 90 fi 91 } 92 #%end 93 else 94 _ble_measure_resolution=1000 # [usec] 95 #%if target == "ksh" 96 # [ksh incompatible code stripped] 97 #%else 98 function ble-measure/.time { 99 ret=0 100 101 local result TIMEFORMAT='[%R]' __ble_n=$1 __ble_command=$2 102 if declare -f ble/util/assign &>/dev/null; then 103 ble/util/assign result '{ time ble-measure/.loop "$__ble_n" "$__ble_command" &>/dev/null;} 2>&1' 104 else 105 result=$({ time ble-measure/.loop "$1" "$2" &>/dev/null;} 2>&1) 106 fi 107 108 local rex='\[([0-9]+)(\.([0-9]+))?\]' 109 [[ $result =~ $rex ]] || return 1 110 local s=${BASH_REMATCH[1]} 111 local ms=${BASH_REMATCH[3]}000; ms=${ms::3} 112 ((ret=(10#0$s*1000+10#0$ms)*1000)) 113 return 0 114 } 115 #%end 116 fi 117 118 _ble_measure_base= # [nsec] 119 _ble_measure_base_nestcost=0 # [nsec/10] 120 #%if target == "ksh" 121 #%% $ echo typeset -a _ble_measure_base_real 122 #%% $ echo typeset -a _ble_measure_base_guess 123 #%else 124 _ble_measure_base_real=() 125 _ble_measure_base_guess=() 126 #%end 127 _ble_measure_count=1 # 同じ倍率で _ble_measure_count 回計測して最小を取る。 128 _ble_measure_threshold=100000 # 一回の計測が threshold [usec] 以上になるようにする 129 130 #%if target != "ksh" 131 ## @fn ble-measure/calibrate 132 function ble-measure/calibrate.0 { ble-measure -qc"$calibrate_count" ''; } 133 function ble-measure/calibrate.1 { ble-measure/calibrate.0; } 134 function ble-measure/calibrate.2 { ble-measure/calibrate.1; } 135 function ble-measure/calibrate.3 { ble-measure/calibrate.2; } 136 function ble-measure/calibrate.4 { ble-measure/calibrate.3; } 137 function ble-measure/calibrate.5 { ble-measure/calibrate.4; } 138 function ble-measure/calibrate.6 { ble-measure/calibrate.5; } 139 function ble-measure/calibrate.7 { ble-measure/calibrate.6; } 140 function ble-measure/calibrate.8 { ble-measure/calibrate.7; } 141 function ble-measure/calibrate.9 { ble-measure/calibrate.8; } 142 function ble-measure/calibrate.A { ble-measure/calibrate.9; } 143 function ble-measure/calibrate { 144 local ret= nsec= 145 146 local calibrate_count=1 147 _ble_measure_base=0 148 _ble_measure_base_nestcost=0 149 150 # nest0: calibrate.0 の ble-measure 内部での ${#FUNCNAME[*]} 151 local nest0=$((${#FUNCNAME[@]}+2)) 152 [[ ${ZSH_VERSION-} ]] && nest0=$((${#funcstack[@]}+2)) 153 ble-measure/calibrate.0; local x0=$nsec 154 ble-measure/calibrate.A; local xA=$nsec 155 local nest_cost=$((xA-x0)) 156 _ble_measure_base=$((x0-nest_cost*nest0/10)) 157 _ble_measure_base_nestcost=$nest_cost 158 } 159 function ble-measure/fit { 160 local ret nsec 161 _ble_measure_base=0 162 _ble_measure_base_nestcost=0 163 164 local calibrate_count=10 165 166 local c= nest_level=${#FUNCNAME[@]} 167 for c in {0..9} A; do 168 "ble-measure/calibrate.$c" 169 ble/util/print "$((nest_level++)) $nsec" 170 done > ble-measure-fit.txt 171 172 gnuplot - <<EOF 173 f(x) = a * x + b 174 b=4500;a=100 175 fit f(x) 'ble-measure-fit.txt' via a,b 176 EOF 177 } 178 #%end 179 180 ## @fn ble-measure/.read-arguments.get-optarg 181 ## @var[in] args arg i c 182 ## @var[in,out] iarg 183 ## @var[out] optarg 184 function ble-measure/.read-arguments.get-optarg { 185 if ((i+1<${#arg})); then 186 optarg=${arg:$((i+1))} 187 i=${#arg} 188 return 0 189 elif ((iarg<${#args[@]})); then 190 optarg=${args[iarg++]} 191 return 0 192 else 193 ble/util/print "ble-measure: missing option argument for '-$c'." 194 flags=E$flags 195 return 1 196 fi 197 } 198 199 ## @fn ble-measure/.read-arguments args 200 ## @var[out] flags 201 ## @var[out] command count 202 function ble-measure/.read-arguments { 203 local -a args; args=("$@") 204 local iarg=0 optarg= 205 [[ ${ZSH_VERSION-} && ! -o KSH_ARRAYS ]] && iarg=1 206 while [[ ${args[iarg]} == -* ]]; do 207 local arg=${args[iarg++]} 208 case $arg in 209 (--) break ;; 210 (--help) flags=h$flags ;; 211 (--no-print-progress) flags=V$flags ;; 212 (--*) 213 ble/util/print "ble-measure: unrecognized option '$arg'." 214 flags=E$flags ;; 215 (-?*) 216 local i= c= # Note: zsh prints the values with just "local i c" 217 for ((i=1;i<${#arg};i++)); do 218 c=${arg:$i:1} 219 case $c in 220 (q) flags=qV$flags ;; 221 ([ca]) 222 [[ $c == a ]] && flags=a$flags 223 ble-measure/.read-arguments.get-optarg && count=$optarg ;; 224 (T) 225 ble-measure/.read-arguments.get-optarg && 226 measure_threshold=$optarg ;; 227 (B) 228 ble-measure/.read-arguments.get-optarg && 229 __ble_base=$optarg ;; 230 (*) 231 ble/util/print "ble-measure: unrecognized option '-$c'." 232 flags=E$flags ;; 233 esac 234 done ;; 235 (-) 236 ble/util/print "ble-measure: unrecognized option '$arg'." 237 flags=E$flags ;; 238 esac 239 done 240 local IFS=$' \t\n' 241 if [[ ${ZSH_VERSION-} ]]; then 242 command="${args[$iarg,-1]}" 243 else 244 command="${args[*]:$iarg}" 245 fi 246 [[ $flags != *E* ]] 247 } 248 249 ## @fn ble-measure [-q|-ac COUNT] command 250 ## command を繰り返し実行する事によりその実行時間を計測します。 251 ## -q を指定した時、計測結果を出力しません。 252 ## -c COUNT を指定した時 COUNT 回計測して最小値を採用します。 253 ## -a COUNT を指定した時 COUNT 回計測して平均値を採用します。 254 ## 255 ## @var[out] ret 256 ## 実行時間を usec 単位で返します。 257 ## @var[out] nsec 258 ## 実行時間を nsec 単位で返します。 259 function ble-measure { 260 local __ble_level=${#FUNCNAME[@]} __ble_base= 261 [[ ${ZSH_VERSION-} ]] && __ble_level=${#funcstack[@]} 262 local flags= command= count=$_ble_measure_count 263 local measure_threshold=$_ble_measure_threshold 264 ble-measure/.read-arguments "$@" || return "$?" 265 if [[ $flags == *h* ]]; then 266 ble/util/print-lines \ 267 'usage: ble-measure [-q|-ac COUNT|-TB TIME] [--] COMMAND' \ 268 ' Measure the time of command.' \ 269 '' \ 270 ' Options:' \ 271 ' -q Do not print results to stdout.' \ 272 ' -a COUNT Measure COUNT times and average.' \ 273 ' -c COUNT Measure COUNT times and take minimum.' \ 274 ' -T TIME Set minimal measuring time.' \ 275 ' -B BASE Set base time (overhead of ble-measure).' \ 276 ' -- The rest arguments are treated as command.' \ 277 ' --help Print this help.' \ 278 '' \ 279 ' Arguments:' \ 280 ' COMMAND Command to be executed repeatedly.' \ 281 '' \ 282 ' Exit status:' \ 283 ' Returns 1 for the failure in measuring the time. Returns 2 after printing' \ 284 ' help. Otherwise, returns 0.' 285 return 2 286 fi 287 288 if [[ ! $__ble_base ]]; then 289 if [[ $_ble_measure_base ]]; then 290 # ble-measure/calibrate 実行済みの時 291 __ble_base=$((_ble_measure_base+_ble_measure_base_nestcost*__ble_level/10)) 292 else 293 # それ以外の時は __ble_level 毎に計測 294 if [[ ! $ble_measure_calibrate && ! ${_ble_measure_base_guess[__ble_level]} ]]; then 295 if [[ ! ${_ble_measure_base_real[__ble_level+1]} ]]; then 296 if [[ ${_ble_measure_target-} == ksh ]]; then 297 # Note: In ksh, we cannot do recursive call with dynamic scoping, 298 # so we directly call the measuring function 299 ble-measure/.time 50000 '' 300 ((nsec=ret*1000/50000)) 301 else 302 local ble_measure_calibrate=1 303 ble-measure -qc3 -B 0 '' 304 ble/util/unlocal ble_measure_calibrate 305 fi 306 _ble_measure_base_real[__ble_level+1]=$nsec 307 _ble_measure_base_guess[__ble_level+1]=$nsec 308 fi 309 310 # 上の実測値は一つ上のレベル (__ble_level+1) での結果になるので現在のレベル 311 # (__ble_level) の値に補正する。レベル毎の時間が chatoyancy での線形フィッ 312 # トの結果に比例する仮定して補正を行う。 313 # 314 # linear-fit result with $f(x) = A x + B$ in chatoyancy 315 # A = 65.9818 pm 2.945 (4.463%) 316 # B = 4356.75 pm 19.97 (0.4585%) 317 local cA=6598 cB=435675 318 nsec=${_ble_measure_base_real[__ble_level+1]} 319 _ble_measure_base_guess[__ble_level]=$((nsec*(cB+cA*(__ble_level-1))/(cB+cA*__ble_level))) 320 ble/util/unlocal cA cB 321 fi 322 __ble_base=${_ble_measure_base_guess[__ble_level]:-0} 323 fi 324 fi 325 326 local __ble_max_n=500000 327 local prev_n= prev_utot= 328 local -i n 329 for n in {1,10,100,1000,10000,100000}\*{1,2,5}; do 330 [[ $prev_n ]] && ((n/prev_n<=10 && prev_utot*n/prev_n<measure_threshold*2/5 && n!=50000)) && continue 331 332 local utot=0 333 [[ $flags != *V* ]] && printf '%s (x%d)...' "$command" "$n" >&2 334 ble-measure/.time "$n" "$command" || return 1 335 [[ $flags != *V* ]] && printf '\r\e[2K' >&2 336 ((utot=ret,utot>=measure_threshold||n==__ble_max_n)) || continue 337 338 prev_n=$n prev_utot=$utot 339 local min_utot=$utot 340 341 # 繰り返し計測して最小値 (-a の時は平均値) を採用 342 if [[ $count ]]; then 343 local sum_utot=$utot sum_count=1 i 344 for ((i=2;i<=count;i++)); do 345 [[ $flags != *V* ]] && printf '%s' "$command (x$n $i/$count)..." >&2 346 if ble-measure/.time "$n" "$command"; then 347 ((utot=ret,utot<min_utot)) && min_utot=$utot 348 ((sum_utot+=utot,sum_count++)) 349 fi 350 [[ $flags != *V* ]] && printf '\r\e[2K' >&2 351 done 352 if [[ $flags == *a* ]]; then 353 ((utot=sum_utot/sum_count)) 354 else 355 utot=$min_utot 356 fi 357 fi 358 359 # upate base if the result is shorter than base 360 if ((min_utot<0x7FFFFFFFFFFFFFFF/1000)); then 361 local __ble_real=$((min_utot*1000/n)) 362 [[ ${_ble_measure_base_real[__ble_level]} ]] && 363 ((__ble_real<_ble_measure_base_real[__ble_level])) && 364 _ble_measure_base_real[__ble_level]=$__ble_real 365 [[ ${_ble_measure_base_guess[__ble_level]} ]] && 366 ((__ble_real<_ble_measure_base_guess[__ble_level])) && 367 _ble_measure_base_guess[__ble_level]=$__ble_real 368 ((__ble_real<__ble_base)) && 369 __ble_base=$__ble_real 370 fi 371 372 local nsec0=$__ble_base 373 if [[ $flags != *q* ]]; then 374 local reso=$_ble_measure_resolution 375 local awk=ble/bin/awk 376 type "$awk" &>/dev/null || awk=awk 377 local -x title="$command (x$n)" 378 "$awk" -v utot="$utot" -v nsec0="$nsec0" -v n="$n" -v reso="$reso" ' 379 function genround(x, mod) { return int(x / mod + 0.5) * mod; } 380 BEGIN { title = ENVIRON["title"]; printf("%12.3f usec/eval: %s\n", genround(utot / n - nsec0 / 1000, reso / 10.0 / n), title); exit }' 381 fi 382 383 local out 384 ((out=utot/n)) 385 if ((n>=1000)); then 386 ((nsec=utot/(n/1000))) 387 else 388 ((nsec=utot*1000/n)) 389 fi 390 ((out-=nsec0/1000,nsec-=nsec0)) 391 ret=$out 392 return 0 393 done 394 } 395 #%end 396 #%if target == "ksh" 397 #%% # varname and command names 398 #%% define 1 1.r|builtin || 399 #%% define 1 1.r| local | typeset | 400 #%% define 1 1.r|ble_measure_calibrate|_ble_measure_calibrate| 401 #%% # function names 402 #%% define 1 1.r|ble/util/unlocal|_ble_util_unlocal| 403 #%% define 1 1.r|ble/util/print-lines|_ble_util_print_lines| 404 #%% define 1 1.r|ble/util/print|_ble_util_print| 405 #%% define 1 1.r|ble-measure/.loop|_ble_measure__loop| 406 #%% define 1 1.r|ble-measure/.get-realtime|_ble_measure__get_realtime| 407 #%% define 1 1.r|ble-measure/.time|_ble_measure__time| 408 #%% define 1 1.r|ble-measure/.read-arguments.get-optarg|_ble_measure__read_arguments_get_optarg| 409 #%% define 1 1.r|ble-measure/.read-arguments|_ble_measure__read_arguments| 410 #%% define 1 1.r|ble-measure|ble_measure| 411 #%% # function defs 412 #%% define 1 1.r|function _ble_measure__time|_ble_measure__time()| 413 #%% define 1 1.r|function _ble_measure__get_realtime|_ble_measure__get_realtime()| 414 #%% define 1 1.r|function _ble_util_unlocal|_ble_util_unlocal()| 415 #%% define 1 1.r|function _ble_measure__read_arguments_get_optarg|_ble_measure__read_arguments_get_optarg()| 416 #%% define 1 1.r|function _ble_measure__read_arguments|_ble_measure__read_arguments()| 417 #%% define 1 1.r|function ble_measure|ble_measure()| 418 #%end 419 #%expand 1