306 lines
10 KiB
Bash
306 lines
10 KiB
Bash
#!/usr/bin/env ksh
|
|
|
|
#!/bin/bash
|
|
|
|
_ble_measure_target=ksh
|
|
if ! type _ble_util_print &>/dev/null; then
|
|
_ble_util_unlocal() { unset -v "$@"; }
|
|
function _ble_util_print { printf '%s\n' "$1"; }
|
|
function _ble_util_print_lines { printf '%s\n' "$@"; }
|
|
fi
|
|
|
|
function _ble_measure__loop {
|
|
# Note: ksh requires to quote ;
|
|
eval "function _target { ${2:+"$2; "}return 0; }"
|
|
typeset __ble_i __ble_n=$1
|
|
for ((__ble_i=0;__ble_i<__ble_n;__ble_i++)); do
|
|
_target
|
|
done
|
|
}
|
|
|
|
## @fn _ble_measure__time n command
|
|
## @param[in] n command
|
|
## @var[out] ret
|
|
## 計測にかかった総時間を μs 単位で返します。
|
|
if ((BASH_VERSINFO[0]>=5)) ||
|
|
{ [[ ${ZSH_VERSION-} ]] && zmodload zsh/datetime &>/dev/null && [[ ${EPOCHREALTIME-} ]]; } ||
|
|
[[ ${SECONDS-} == *.??? ]]
|
|
then
|
|
## @fn _ble_measure__get_realtime
|
|
## @var[out] ret
|
|
if [[ ${EPOCHREALTIME-} ]]; then
|
|
_ble_measure_resolution=1 # [usec]
|
|
_ble_measure__get_realtime() {
|
|
typeset LC_ALL= LC_NUMERIC=C
|
|
ret=$EPOCHREALTIME
|
|
}
|
|
else
|
|
# Note: ksh does not have "local"-equivalent for the POSIX-style functions,
|
|
# so we do not set the locale here. Anyway, we do not care the
|
|
# interference with outer-scope variables since this script is used
|
|
# limitedly in ksh.
|
|
_ble_measure_resolution=1000 # [usec]
|
|
_ble_measure__get_realtime() {
|
|
ret=$SECONDS
|
|
}
|
|
fi
|
|
_ble_measure__time() {
|
|
_ble_measure__get_realtime 2>/dev/null; typeset __ble_time1=$ret
|
|
_ble_measure__loop "$1" "$2" &>/dev/null
|
|
_ble_measure__get_realtime 2>/dev/null; typeset __ble_time2=$ret
|
|
|
|
# convert __ble_time1 and __ble_time2 to usec
|
|
# Note: ksh does not support empty index as ${__ble_frac::6}.
|
|
typeset __ble_frac
|
|
[[ $__ble_time1 == *.* ]] || __ble_time1=${__ble_time1}.
|
|
__ble_frac=${__ble_time1##*.}000000 __ble_time1=${__ble_time1%%.*}${__ble_frac:0:6}
|
|
[[ $__ble_time2 == *.* ]] || __ble_time2=${__ble_time2}.
|
|
__ble_frac=${__ble_time2##*.}000000 __ble_time2=${__ble_time2%%.*}${__ble_frac:0:6}
|
|
|
|
((ret=__ble_time2-__ble_time1))
|
|
((ret==0&&(ret=_ble_measure_resolution)))
|
|
((ret>0))
|
|
}
|
|
elif [[ ${ZSH_VERSION-} ]]; then
|
|
_ble_measure_resolution=1000 # [usec]
|
|
# [ksh incompatible code stripped]
|
|
else
|
|
_ble_measure_resolution=1000 # [usec]
|
|
# [ksh incompatible code stripped]
|
|
fi
|
|
|
|
_ble_measure_base= # [nsec]
|
|
_ble_measure_base_nestcost=0 # [nsec/10]
|
|
typeset -a _ble_measure_base_real
|
|
typeset -a _ble_measure_base_guess
|
|
_ble_measure_count=1 # 同じ倍率で _ble_measure_count 回計測して最小を取る。
|
|
_ble_measure_threshold=100000 # 一回の計測が threshold [usec] 以上になるようにする
|
|
|
|
|
|
## @fn _ble_measure__read_arguments_get_optarg
|
|
## @var[in] args arg i c
|
|
## @var[in,out] iarg
|
|
## @var[out] optarg
|
|
_ble_measure__read_arguments_get_optarg() {
|
|
if ((i+1<${#arg})); then
|
|
optarg=${arg:$((i+1))}
|
|
i=${#arg}
|
|
return 0
|
|
elif ((iarg<${#args[@]})); then
|
|
optarg=${args[iarg++]}
|
|
return 0
|
|
else
|
|
_ble_util_print "ble_measure: missing option argument for '-$c'."
|
|
flags=E$flags
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
## @fn _ble_measure__read_arguments args
|
|
## @var[out] flags
|
|
## @var[out] command count
|
|
_ble_measure__read_arguments() {
|
|
typeset -a args; args=("$@")
|
|
typeset iarg=0 optarg=
|
|
[[ ${ZSH_VERSION-} && ! -o KSH_ARRAYS ]] && iarg=1
|
|
while [[ ${args[iarg]} == -* ]]; do
|
|
typeset arg=${args[iarg++]}
|
|
case $arg in
|
|
(--) break ;;
|
|
(--help) flags=h$flags ;;
|
|
(--no-print-progress) flags=V$flags ;;
|
|
(--*)
|
|
_ble_util_print "ble_measure: unrecognized option '$arg'."
|
|
flags=E$flags ;;
|
|
(-?*)
|
|
typeset i= c= # Note: zsh prints the values with just "local i c"
|
|
for ((i=1;i<${#arg};i++)); do
|
|
c=${arg:$i:1}
|
|
case $c in
|
|
(q) flags=qV$flags ;;
|
|
([ca])
|
|
[[ $c == a ]] && flags=a$flags
|
|
_ble_measure__read_arguments_get_optarg && count=$optarg ;;
|
|
(T)
|
|
_ble_measure__read_arguments_get_optarg &&
|
|
measure_threshold=$optarg ;;
|
|
(B)
|
|
_ble_measure__read_arguments_get_optarg &&
|
|
__ble_base=$optarg ;;
|
|
(*)
|
|
_ble_util_print "ble_measure: unrecognized option '-$c'."
|
|
flags=E$flags ;;
|
|
esac
|
|
done ;;
|
|
(-)
|
|
_ble_util_print "ble_measure: unrecognized option '$arg'."
|
|
flags=E$flags ;;
|
|
esac
|
|
done
|
|
typeset IFS=$' \t\n'
|
|
if [[ ${ZSH_VERSION-} ]]; then
|
|
command="${args[$iarg,-1]}"
|
|
else
|
|
command="${args[*]:$iarg}"
|
|
fi
|
|
[[ $flags != *E* ]]
|
|
}
|
|
|
|
## @fn ble_measure [-q|-ac COUNT] command
|
|
## command を繰り返し実行する事によりその実行時間を計測します。
|
|
## -q を指定した時、計測結果を出力しません。
|
|
## -c COUNT を指定した時 COUNT 回計測して最小値を採用します。
|
|
## -a COUNT を指定した時 COUNT 回計測して平均値を採用します。
|
|
##
|
|
## @var[out] ret
|
|
## 実行時間を usec 単位で返します。
|
|
## @var[out] nsec
|
|
## 実行時間を nsec 単位で返します。
|
|
ble_measure() {
|
|
eval -- "${_ble_bash_POSIXLY_CORRECT_local_adjust-}"
|
|
typeset __ble_level=${#FUNCNAME[@]} __ble_base=
|
|
[[ ${ZSH_VERSION-} ]] && __ble_level=${#funcstack[@]}
|
|
typeset flags= command= count=$_ble_measure_count
|
|
typeset measure_threshold=$_ble_measure_threshold
|
|
_ble_measure__read_arguments "$@"; typeset ext=$?
|
|
if ((ext)); then
|
|
eval -- "${_ble_bash_POSIXLY_CORRECT_local_leave-}"
|
|
return "$ext"
|
|
fi
|
|
|
|
if [[ $flags == *h* ]]; then
|
|
_ble_util_print_lines \
|
|
'usage: ble_measure [-q|-ac COUNT|-TB TIME] [--] COMMAND' \
|
|
' Measure the time of command.' \
|
|
'' \
|
|
' Options:' \
|
|
' -q Do not print results to stdout.' \
|
|
' -a COUNT Measure COUNT times and average.' \
|
|
' -c COUNT Measure COUNT times and take minimum.' \
|
|
' -T TIME Set minimal measuring time.' \
|
|
' -B BASE Set base time (overhead of ble_measure).' \
|
|
' -- The rest arguments are treated as command.' \
|
|
' --help Print this help.' \
|
|
'' \
|
|
' Arguments:' \
|
|
' COMMAND Command to be executed repeatedly.' \
|
|
'' \
|
|
' Exit status:' \
|
|
' Returns 1 for the failure in measuring the time. Returns 2 after printing' \
|
|
' help. Otherwise, returns 0.'
|
|
eval -- "${_ble_bash_POSIXLY_CORRECT_local_leave-}"
|
|
return 2
|
|
fi
|
|
|
|
if [[ ! $__ble_base ]]; then
|
|
if [[ $_ble_measure_base ]]; then
|
|
# ble_measure/calibrate 実行済みの時
|
|
__ble_base=$((_ble_measure_base+_ble_measure_base_nestcost*__ble_level/10))
|
|
else
|
|
# それ以外の時は __ble_level 毎に計測
|
|
if [[ ! $_ble_measure_calibrate && ! ${_ble_measure_base_guess[__ble_level]} ]]; then
|
|
if [[ ! ${_ble_measure_base_real[__ble_level+1]} ]]; then
|
|
if [[ ${_ble_measure_target-} == ksh ]]; then
|
|
# Note: In ksh, we cannot do recursive call with dynamic scoping,
|
|
# so we directly call the measuring function
|
|
_ble_measure__time 50000 ''
|
|
((nsec=ret*1000/50000))
|
|
else
|
|
typeset _ble_measure_calibrate=1
|
|
ble_measure -qc3 -B 0 ''
|
|
_ble_util_unlocal _ble_measure_calibrate
|
|
fi
|
|
_ble_measure_base_real[__ble_level+1]=$nsec
|
|
_ble_measure_base_guess[__ble_level+1]=$nsec
|
|
fi
|
|
|
|
# 上の実測値は一つ上のレベル (__ble_level+1) での結果になるので現在のレベル
|
|
# (__ble_level) の値に補正する。レベル毎の時間が chatoyancy での線形フィッ
|
|
# トの結果に比例する仮定して補正を行う。
|
|
#
|
|
# linear-fit result with $f(x) = A x + B$ in chatoyancy
|
|
# A = 65.9818 pm 2.945 (4.463%)
|
|
# B = 4356.75 pm 19.97 (0.4585%)
|
|
typeset cA=6598 cB=435675
|
|
nsec=${_ble_measure_base_real[__ble_level+1]}
|
|
_ble_measure_base_guess[__ble_level]=$((nsec*(cB+cA*(__ble_level-1))/(cB+cA*__ble_level)))
|
|
_ble_util_unlocal cA cB
|
|
fi
|
|
__ble_base=${_ble_measure_base_guess[__ble_level]:-0}
|
|
fi
|
|
fi
|
|
|
|
typeset __ble_max_n=500000
|
|
typeset prev_n= prev_utot=
|
|
typeset -i n
|
|
for n in {1,10,100,1000,10000,100000}\*{1,2,5}; do
|
|
[[ $prev_n ]] && ((n/prev_n<=10 && prev_utot*n/prev_n<measure_threshold*2/5 && n!=50000)) && continue
|
|
|
|
typeset utot=0
|
|
[[ $flags != *V* ]] && printf '%s (x%d)...' "$command" "$n" >&2
|
|
if ! _ble_measure__time "$n" "$command"; then
|
|
eval -- "${_ble_bash_POSIXLY_CORRECT_local_leave-}"
|
|
return 1
|
|
fi
|
|
[[ $flags != *V* ]] && printf '\r\e[2K' >&2
|
|
((utot=ret,utot>=measure_threshold||n==__ble_max_n)) || continue
|
|
|
|
prev_n=$n prev_utot=$utot
|
|
typeset min_utot=$utot
|
|
|
|
# 繰り返し計測して最小値 (-a の時は平均値) を採用
|
|
if [[ $count ]]; then
|
|
typeset sum_utot=$utot sum_count=1 i
|
|
for ((i=2;i<=count;i++)); do
|
|
[[ $flags != *V* ]] && printf '%s' "$command (x$n $i/$count)..." >&2
|
|
if _ble_measure__time "$n" "$command"; then
|
|
((utot=ret,utot<min_utot)) && min_utot=$utot
|
|
((sum_utot+=utot,sum_count++))
|
|
fi
|
|
[[ $flags != *V* ]] && printf '\r\e[2K' >&2
|
|
done
|
|
if [[ $flags == *a* ]]; then
|
|
((utot=sum_utot/sum_count))
|
|
else
|
|
utot=$min_utot
|
|
fi
|
|
fi
|
|
|
|
# update base if the result is shorter than base
|
|
if ((min_utot<0x7FFFFFFFFFFFFFFF/1000)); then
|
|
typeset __ble_real=$((min_utot*1000/n))
|
|
[[ ${_ble_measure_base_real[__ble_level]} ]] &&
|
|
((__ble_real<_ble_measure_base_real[__ble_level])) &&
|
|
_ble_measure_base_real[__ble_level]=$__ble_real
|
|
[[ ${_ble_measure_base_guess[__ble_level]} ]] &&
|
|
((__ble_real<_ble_measure_base_guess[__ble_level])) &&
|
|
_ble_measure_base_guess[__ble_level]=$__ble_real
|
|
((__ble_real<__ble_base)) &&
|
|
__ble_base=$__ble_real
|
|
fi
|
|
|
|
typeset nsec0=$__ble_base
|
|
if [[ $flags != *q* ]]; then
|
|
typeset reso=$_ble_measure_resolution
|
|
typeset awk=ble/bin/awk
|
|
type -- "$awk" &>/dev/null || awk=awk
|
|
typeset -x title="$command (x$n)"
|
|
"$awk" -v utot="$utot" -v nsec0="$nsec0" -v n="$n" -v reso="$reso" '
|
|
function genround(x, mod) { return int(x / mod + 0.5) * mod; }
|
|
BEGIN { title = ENVIRON["title"]; printf("%12.3f usec/eval: %s\n", genround(utot / n - nsec0 / 1000, reso / 10.0 / n), title); exit }'
|
|
fi
|
|
|
|
typeset out
|
|
((out=utot/n))
|
|
if ((n>=1000)); then
|
|
((nsec=utot/(n/1000)))
|
|
else
|
|
((nsec=utot*1000/n))
|
|
fi
|
|
((out-=nsec0/1000,nsec-=nsec0))
|
|
ret=$out
|
|
eval -- "${_ble_bash_POSIXLY_CORRECT_local_leave-}"
|
|
return 0
|
|
done
|
|
eval -- "${_ble_bash_POSIXLY_CORRECT_local_return-}"
|
|
}
|