使用 Bash 计算程序的平均执行时间
Calculate average execution time of a program using Bash
要获得任何可执行文件的执行时间,比如说a.out
,我可以简单地写time ./a.out
。这将输出实时、用户时间和系统时间。
可否写一个bash脚本,多次运行程序,计算并输出平均实际执行时间?
记录执行的开始和结束时间,然后用差值除以执行次数,这样可能更容易。
#!/bin/bash
times=10
start=$(date +%s)
for ((i=0; i < times; i++)) ; do
run_your_executable_here
done
end=$(date +%s)
bc -l <<< "($end - $start) / $times"
我使用 bc
来计算平均值,因为 bash 不支持浮点运算。
要获得更高的精度,您可以切换到纳秒:
start=$(date +%s.%N)
$end
.
也类似
您可以编写一个循环并收集 time
命令的输出并将其通过管道传输到 awk
以计算平均值:
avg_time() {
#
# usage: avg_time n command ...
#
n=; shift
(($# > 0)) || return # bail if no command given
for ((i = 0; i < n; i++)); do
{ time -p "$@" &>/dev/null; } 2>&1 # ignore the output of the command
# but collect time's output in stdout
done | awk '
/real/ { real = real + ; nr++ }
/user/ { user = user + ; nu++ }
/sys/ { sys = sys + ; ns++}
END {
if (nr>0) printf("real %f\n", real/nr);
if (nu>0) printf("user %f\n", user/nu);
if (ns>0) printf("sys %f\n", sys/ns)
}'
}
示例:
avg_time 5 sleep 1
会给你
real 1.000000
user 0.000000
sys 0.000000
这可以很容易地增强为:
- 在两次执行之间休眠一段给定的时间
- 在执行之间随机休眠一段时间(在一定范围内)
来自man time
的time -p
的含义:
-p
When in the POSIX locale, use the precise traditional format
"real %f\nuser %f\nsys %f\n"
(with numbers in seconds) where the number of decimals in the
output for %f is unspecified but is sufficient to express the
clock tick accuracy, and at least one.
您可能还想看看这个命令行基准测试工具:
总执行时间与单次执行时间之和
小心!除以N四舍五入的和执行时间不精确!
相反,我们可以除以 N 次迭代的总执行时间(除以 N)
avg_time_alt() {
local -i n=
local foo real sys user
shift
(($# > 0)) || return;
{ read foo real; read foo user; read foo sys ;} < <(
{ time -p for((;n--;)){ "$@" &>/dev/null ;} ;} 2>&1
)
printf "real: %.5f\nuser: %.5f\nsys : %.5f\n" $(
bc -l <<<"$real/$n;$user/$n;$sys/$n;" )
}
注意: 这使用 bc
而不是 awk
来计算平均值。为此,我们将创建一个临时 bc
文件:
printf >/tmp/test-pi.bc "scale=%d;\npi=4*a(1);\nquit\n" 60
这将计算 ¶
后 60 位小数,然后安静地退出。 (您可以为您的主机调整小数位数。)
演示:
avg_time_alt 1000 sleep .001
real: 0.00195
user: 0.00008
sys : 0.00016
avg_time_alt 1000 bc -ql /tmp/test-pi.bc
real: 0.00172
user: 0.00120
sys : 0.00058
其中 将回答:
avg_time 1000 sleep .001
real 0.000000
user 0.000000
sys 0.000000
avg_time 1000 bc -ql /tmp/test-pi.bc
real 0.000000
user 0.000000
sys 0.000000
替代方案,受启发,使用Linux的/proc
好的,你可以考虑:
avgByProc() {
local foo start end n= e= values times
shift;
export n;
{
read foo;
read foo;
read foo foo start foo
} < /proc/timer_list;
mapfile values < <(
for((;n--;)){ "$@" &>/dev/null;}
read -a endstat < /proc/self/stat
{
read foo
read foo
read foo foo end foo
} </proc/timer_list
printf -v times "%s/100/$e;" ${endstat[@]:13:4}
bc -l <<<"$[end-start]/10^9/$e;$times"
)
printf -v fmt "%-7s: %%.5f\n" real utime stime cutime cstime
printf "$fmt" ${values[@]}
}
这是基于/proc
:
man 5 proc | grep [su]time\\|timer.list | sed 's/^/> /'
(14) utime %lu
(15) stime %lu
(16) cutime %ld
(17) cstime %ld
/proc/timer_list (since Linux 2.6.21)
然后现在:
avgByProc 1000 sleep .001
real : 0.00242
utime : 0.00015
stime : 0.00021
cutime : 0.00082
cstime : 0.00020
其中utime
和stime
代表用户时间和系统时间bash自己cutime
和 cstime
代表 子用户时间 和 子系统时间 是最有趣的.
注意:在这种情况下(sleep
)命令不会使用大量资源。
avgByProc 1000 bc -ql /tmp/test-pi.bc
real : 0.00175
utime : 0.00015
stime : 0.00025
cutime : 0.00108
cstime : 0.00032
这变得更清楚了...
当然,作为连续访问timer_list
和self/stat
而不是原子,real
之间的差异(纳秒基于)和c?[su]time
(基于ticks即:1/100秒)可能会出现!
- 将 (,) 转换为 (.) 以获得 i18n 支持
- 硬编码为 10,根据需要进行调整
- returns 只有“真实”值,您最可能想要的值
单线
for i in {1..10}; do time $@; done 2>&1 | grep ^real | sed s/,/./ | sed -e s/.*m// | awk '{sum += } END {print sum / NR}'
我做了一个“更完整”的版本
- 输出每次执行的结果,让您知道执行了正确的事情
- 每 运行 次显示一次,因此您可以浏览异常值
但实际上,如果您需要高级的东西,只需使用 hyperfine。
GREEN='3[0;32m'
PURPLE='3[0;35m'
RESET='3[0m'
# example: perf sleep 0.001
# https://serverfault.com/questions/175376/redirect-output-of-time-command-in-unix-into-a-variable-in-bash
perfFull() {
TIMEFORMAT=%R # `time` outputs only a number, not 3 lines
export LC_NUMERIC="en_US.UTF-8" # `time` outputs `0.100` instead of local format, like `0,100`
times=10
echo -e -n "\nWARMING UP ${PURPLE}$@${RESET}"
$@ # execute passed parameters
echo -e -n "RUNNING ${PURPLE}$times times${RESET}"
exec 3>&1 4>&2 # redirects subshell streams
durations=()
for _ in `seq $times`; {
durations+=(`{ time $@ 1>&3 2>&4; } 2>&1`) # passes stdout through so only `time` is caputured
}
exec 3>&- 4>&- # reset subshell streams
printf '%s\n' "${durations[@]}"
total=0
for duration in "${durations[@]}"; {
total=$(bc <<< "scale=3;$total + $duration")
}
average=($(bc <<< "scale=3;$total/$times"))
echo -e "${GREEN}$average average${RESET}"
}
要获得任何可执行文件的执行时间,比如说a.out
,我可以简单地写time ./a.out
。这将输出实时、用户时间和系统时间。
可否写一个bash脚本,多次运行程序,计算并输出平均实际执行时间?
记录执行的开始和结束时间,然后用差值除以执行次数,这样可能更容易。
#!/bin/bash
times=10
start=$(date +%s)
for ((i=0; i < times; i++)) ; do
run_your_executable_here
done
end=$(date +%s)
bc -l <<< "($end - $start) / $times"
我使用 bc
来计算平均值,因为 bash 不支持浮点运算。
要获得更高的精度,您可以切换到纳秒:
start=$(date +%s.%N)
$end
.
您可以编写一个循环并收集 time
命令的输出并将其通过管道传输到 awk
以计算平均值:
avg_time() {
#
# usage: avg_time n command ...
#
n=; shift
(($# > 0)) || return # bail if no command given
for ((i = 0; i < n; i++)); do
{ time -p "$@" &>/dev/null; } 2>&1 # ignore the output of the command
# but collect time's output in stdout
done | awk '
/real/ { real = real + ; nr++ }
/user/ { user = user + ; nu++ }
/sys/ { sys = sys + ; ns++}
END {
if (nr>0) printf("real %f\n", real/nr);
if (nu>0) printf("user %f\n", user/nu);
if (ns>0) printf("sys %f\n", sys/ns)
}'
}
示例:
avg_time 5 sleep 1
会给你
real 1.000000
user 0.000000
sys 0.000000
这可以很容易地增强为:
- 在两次执行之间休眠一段给定的时间
- 在执行之间随机休眠一段时间(在一定范围内)
来自man time
的time -p
的含义:
-p When in the POSIX locale, use the precise traditional format "real %f\nuser %f\nsys %f\n" (with numbers in seconds) where the number of decimals in the output for %f is unspecified but is sufficient to express the clock tick accuracy, and at least one.
您可能还想看看这个命令行基准测试工具:
总执行时间与单次执行时间之和
小心!除以N四舍五入的和执行时间不精确!
相反,我们可以除以 N 次迭代的总执行时间(除以 N)
avg_time_alt() {
local -i n=
local foo real sys user
shift
(($# > 0)) || return;
{ read foo real; read foo user; read foo sys ;} < <(
{ time -p for((;n--;)){ "$@" &>/dev/null ;} ;} 2>&1
)
printf "real: %.5f\nuser: %.5f\nsys : %.5f\n" $(
bc -l <<<"$real/$n;$user/$n;$sys/$n;" )
}
注意: 这使用 bc
而不是 awk
来计算平均值。为此,我们将创建一个临时 bc
文件:
printf >/tmp/test-pi.bc "scale=%d;\npi=4*a(1);\nquit\n" 60
这将计算 ¶
后 60 位小数,然后安静地退出。 (您可以为您的主机调整小数位数。)
演示:
avg_time_alt 1000 sleep .001
real: 0.00195
user: 0.00008
sys : 0.00016
avg_time_alt 1000 bc -ql /tmp/test-pi.bc
real: 0.00172
user: 0.00120
sys : 0.00058
其中
avg_time 1000 sleep .001
real 0.000000
user 0.000000
sys 0.000000
avg_time 1000 bc -ql /tmp/test-pi.bc
real 0.000000
user 0.000000
sys 0.000000
替代方案,受启发,使用Linux的/proc
/proc
好的,你可以考虑:
avgByProc() {
local foo start end n= e= values times
shift;
export n;
{
read foo;
read foo;
read foo foo start foo
} < /proc/timer_list;
mapfile values < <(
for((;n--;)){ "$@" &>/dev/null;}
read -a endstat < /proc/self/stat
{
read foo
read foo
read foo foo end foo
} </proc/timer_list
printf -v times "%s/100/$e;" ${endstat[@]:13:4}
bc -l <<<"$[end-start]/10^9/$e;$times"
)
printf -v fmt "%-7s: %%.5f\n" real utime stime cutime cstime
printf "$fmt" ${values[@]}
}
这是基于/proc
:
man 5 proc | grep [su]time\\|timer.list | sed 's/^/> /' (14) utime %lu (15) stime %lu (16) cutime %ld (17) cstime %ld /proc/timer_list (since Linux 2.6.21)
然后现在:
avgByProc 1000 sleep .001
real : 0.00242
utime : 0.00015
stime : 0.00021
cutime : 0.00082
cstime : 0.00020
其中utime
和stime
代表用户时间和系统时间bash自己cutime
和 cstime
代表 子用户时间 和 子系统时间 是最有趣的.
注意:在这种情况下(sleep
)命令不会使用大量资源。
avgByProc 1000 bc -ql /tmp/test-pi.bc
real : 0.00175
utime : 0.00015
stime : 0.00025
cutime : 0.00108
cstime : 0.00032
这变得更清楚了...
当然,作为连续访问timer_list
和self/stat
而不是原子,real
之间的差异(纳秒基于)和c?[su]time
(基于ticks即:1/100秒)可能会出现!
- 将 (,) 转换为 (.) 以获得 i18n 支持
- 硬编码为 10,根据需要进行调整
- returns 只有“真实”值,您最可能想要的值
单线
for i in {1..10}; do time $@; done 2>&1 | grep ^real | sed s/,/./ | sed -e s/.*m// | awk '{sum += } END {print sum / NR}'
我做了一个“更完整”的版本
- 输出每次执行的结果,让您知道执行了正确的事情
- 每 运行 次显示一次,因此您可以浏览异常值
但实际上,如果您需要高级的东西,只需使用 hyperfine。
GREEN='3[0;32m'
PURPLE='3[0;35m'
RESET='3[0m'
# example: perf sleep 0.001
# https://serverfault.com/questions/175376/redirect-output-of-time-command-in-unix-into-a-variable-in-bash
perfFull() {
TIMEFORMAT=%R # `time` outputs only a number, not 3 lines
export LC_NUMERIC="en_US.UTF-8" # `time` outputs `0.100` instead of local format, like `0,100`
times=10
echo -e -n "\nWARMING UP ${PURPLE}$@${RESET}"
$@ # execute passed parameters
echo -e -n "RUNNING ${PURPLE}$times times${RESET}"
exec 3>&1 4>&2 # redirects subshell streams
durations=()
for _ in `seq $times`; {
durations+=(`{ time $@ 1>&3 2>&4; } 2>&1`) # passes stdout through so only `time` is caputured
}
exec 3>&- 4>&- # reset subshell streams
printf '%s\n' "${durations[@]}"
total=0
for duration in "${durations[@]}"; {
total=$(bc <<< "scale=3;$total + $duration")
}
average=($(bc <<< "scale=3;$total/$times"))
echo -e "${GREEN}$average average${RESET}"
}