如何配置bash shell脚本慢启动?

And*_*ini 119 bash profiling

我的bash shell需要3-4秒才能启动,而如果我启动它--norc会立即运行.

我开始"分析" /etc/bash.bashrc~/.bashrc通过手动插入return语句和寻求速度改进,但它不是一个定量过程而且效率不高.

如何配置我的bash脚本并查看哪些命令需要花费大部分时间来启动?

Pau*_*ce. 123

如果您有GNU date(或其他可以输出纳秒的版本),请在开头/etc/bash.bashrc(或在任何Bash脚本中开始跟踪的任何地方)执行此操作:

PS4='+ $(date "+%s.%N")\011 '
exec 3>&2 2>/tmp/bashstart.$$.log
set -x
Run Code Online (Sandbox Code Playgroud)

set +x
exec 2>&3 3>&-
Run Code Online (Sandbox Code Playgroud)

~/.bashrc任何Bash脚本部分的末尾(或者你想跟踪停止的部分结尾).

您应该获得一个跟踪日志,\011其中显示了已执行的每个命令的seconds.nanoseconds时间戳.从一个时间到下一个时间的差异是干预步骤所花费的时间量.

当你缩小范围时,你可以更/tmp/bashstart.PID.log晚和set -x更早地移动(或选择性地包括几个感兴趣的部分).

  • ...实际上,使用bash 4.2,可以做得更好 - 在`PS4`中使用`\ D {...}`允许扩展完全任意的时间格式字符串,而不会将"date"作为子进程启动的性能开销. (7认同)
  • @CharlesDuffy:这些都很酷.然而,GNU`date`理解`%N`而Bash 4.2没有(因为`strftime(3)`没有)在GNU系统上 - 因此任意有限制.关于性能与分辨率的观点是一个很好的观点,用户应该明智地做出选择,记住只有在调试期间(仅当`set -x`生效时)性能命中才是暂时的. (3认同)

F. *_*uri 102

分析(4个答案)

编辑:2016年3月添加script方法

阅读本文并因为分析是一个重要的步骤,我已经对整个SO问题进行了一些测试和研究,并且已经发布了答案.

有4个以上的答案:

  • 第一个是基于@DennisWilliamson的想法,但资源消耗较少
  • 第二个是我自己的(在此之前;)
  • 第三个是基于@fgm答案,但更准确.
  • 最后使用script,scriptreplay定时文件.

  • 最后,最后对表演进行一点比较.

使用set -xdate,但有限的

从@ DennisWilliamson的想法中获取,但是使用以下语法,只有一个初始fork到3个命令:

exec 3>&2 2> >(tee /tmp/sample-time.$$.log |
                 sed -u 's/^.*$/now/' |
                 date -f - +%s.%N >/tmp/sample-time.$$.tim)
set -x
Run Code Online (Sandbox Code Playgroud)

这样做只会运行date一次.有一个快速演示/测试,以显示它是如何工作的:

for i in {1..4};do echo now;sleep .05;done| date -f - +%N
Run Code Online (Sandbox Code Playgroud)

示例脚本:

#!/bin/bash

exec 3>&2 2> >( tee /tmp/sample-$$.log |
                  sed -u 's/^.*$/now/' |
                  date -f - +%s.%N >/tmp/sample-$$.tim)
set -x

for ((i=3;i--;));do sleep .1;done

for ((i=2;i--;))
do
    tar -cf /tmp/test.tar -C / bin
    gzip /tmp/test.tar
    rm /tmp/test.tar.gz
done

set +x
exec 2>&3 3>&-
Run Code Online (Sandbox Code Playgroud)

通过运行此脚本,您可以生成2个文件:/tmp/sample-XXXX.log/tmp/sample-XXXX.tim(其中XXXX是运行脚本的进程ID).

您可以使用paste以下方式呈现它们:

paste tmp/sample-XXXX.{tim,log}
Run Code Online (Sandbox Code Playgroud)

或者您甚至可以计算差异时间:

paste <(
    while read tim ;do
        crt=000000000$((${tim//.}-10#0$last))
        printf "%12.9f\n" ${crt:0:${#crt}-9}.${crt:${#crt}-9}
        last=${tim//.}
      done < sample-time.24804.tim
  ) sample-time.24804.log 

 1388487534.391309713        + (( i=3 ))
 0.000080807        + (( i-- ))
 0.000008312        + sleep .1
 0.101304843        + (( 1 ))
 0.000032616        + (( i-- ))
 0.000007124        + sleep .1
 0.101251684        + (( 1 ))
 0.000033036        + (( i-- ))
 0.000007054        + sleep .1
 0.104013813        + (( 1 ))
 0.000026959        + (( i-- ))
 0.000006915        + (( i=2 ))
 0.000006635        + (( i-- ))
 0.000006844        + tar -cf /tmp/test.tar -C / bin
 0.022655107        + gzip /tmp/test.tar
 0.637042668        + rm /tmp/test.tar.gz
 0.000823649        + (( 1 ))
 0.000011314        + (( i-- ))
 0.000006915        + tar -cf /tmp/test.tar -C / bin
 0.016084482        + gzip /tmp/test.tar
 0.627798263        + rm /tmp/test.tar.gz
 0.001294946        + (( 1 ))
 0.000023187        + (( i-- ))
 0.000006845        + set +x
Run Code Online (Sandbox Code Playgroud)

或两列:

paste <(
    while read tim ;do
        [ -z "$last" ] && last=${tim//.} && first=${tim//.}
        crt=000000000$((${tim//.}-10#0$last))
        ctot=000000000$((${tim//.}-10#0$first))
        printf "%12.9f %12.9f\n" ${crt:0:${#crt}-9}.${crt:${#crt}-9} \
                                 ${ctot:0:${#ctot}-9}.${ctot:${#ctot}-9}
        last=${tim//.}
      done < sample-time.24804.tim
  ) sample-time.24804.log
Run Code Online (Sandbox Code Playgroud)

可能呈现:

 0.000000000  0.000000000   + (( i=3 ))
 0.000080807  0.000080807   + (( i-- ))
 0.000008312  0.000089119   + sleep .1
 0.101304843  0.101393962   + (( 1 ))
 0.000032616  0.101426578   + (( i-- ))
 0.000007124  0.101433702   + sleep .1
 0.101251684  0.202685386   + (( 1 ))
 0.000033036  0.202718422   + (( i-- ))
 0.000007054  0.202725476   + sleep .1
 0.104013813  0.306739289   + (( 1 ))
 0.000026959  0.306766248   + (( i-- ))
 0.000006915  0.306773163   + (( i=2 ))
 0.000006635  0.306779798   + (( i-- ))
 0.000006844  0.306786642   + tar -cf /tmp/test.tar -C / bin
 0.022655107  0.329441749   + gzip /tmp/test.tar
 0.637042668  0.966484417   + rm /tmp/test.tar.gz
 0.000823649  0.967308066   + (( 1 ))
 0.000011314  0.967319380   + (( i-- ))
 0.000006915  0.967326295   + tar -cf /tmp/test.tar -C / bin
 0.016084482  0.983410777   + gzip /tmp/test.tar
 0.627798263  1.611209040   + rm /tmp/test.tar.gz
 0.001294946  1.612503986   + (( 1 ))
 0.000023187  1.612527173   + (( i-- ))
 0.000006845  1.612534018   + set +x
Run Code Online (Sandbox Code Playgroud)

使用trap debug/proc/timer_list最近的 GNU/Linux内核,无需 .

GNU/Linux最近的内核中,您可能会找到一个/proc名为的文件timer_list:

grep 'now at\|offset' /proc/timer_list
now at 5461935212966259 nsecs
  .offset:     0 nsecs
  .offset:     1383718821564493249 nsecs
  .offset:     0 nsecs
Run Code Online (Sandbox Code Playgroud)

当前时间是总和5461935212966259 + 1383718821564493249,但是以纳秒为单位.

因此,对于计算经过的时间,不需要知道偏移.

对于这种工作,我编写了elap.bash(V2),它由以下语法提供:

source elap.bash-v2
Run Code Online (Sandbox Code Playgroud)

要么

. elap.bash-v2 init
Run Code Online (Sandbox Code Playgroud)

(有关完整语法,请参阅注释)

所以你可以在脚本的顶部添加这一行:

. elap.bash-v2 trap2
Run Code Online (Sandbox Code Playgroud)

小样本:

#!/bin/bash

. elap.bash-v2 trap

for ((i=3;i--;));do sleep .1;done

elapCalc2
elapShowTotal \\e[1mfirst total\\e[0m

for ((i=2;i--;))
do
    tar -cf /tmp/test.tar -C / bin
    gzip /tmp/test.tar
    rm /tmp/test.tar.gz
done

trap -- debug
elapTotal \\e[1mtotal time\\e[0m
Run Code Online (Sandbox Code Playgroud)

在我的主机上渲染:

 0.000947481 Starting
 0.000796900 ((i=3))
 0.000696956 ((i--))
 0.101969242 sleep .1
 0.000812478 ((1))
 0.000755067 ((i--))
 0.103693305 sleep .1
 0.000730482 ((1))
 0.000660360 ((i--))
 0.103565001 sleep .1
 0.000719516 ((1))
 0.000671325 ((i--))
 0.000754856 elapCalc2
 0.316018113 first total
 0.000754787 elapShowTotal \e[1mfirst total\e[0m
 0.000711275 ((i=2))
 0.000683408 ((i--))
 0.075673816 tar -cf /tmp/test.tar -C / bin
 0.596389329 gzip /tmp/test.tar
 0.006565188 rm /tmp/test.tar.gz
 0.000830217 ((1))
 0.000759466 ((i--))
 0.024783966 tar -cf /tmp/test.tar -C / bin
 0.604119903 gzip /tmp/test.tar
 0.005172940 rm /tmp/test.tar.gz
 0.000952299 ((1))
 0.000827421 ((i--))
 1.635788924 total time
 1.636657204 EXIT
Run Code Online (Sandbox Code Playgroud)

使用trap2而不是trapsource命令的参数:

#!/bin/bash

. elap.bash-v2 trap2
...
Run Code Online (Sandbox Code Playgroud)

将渲染两列最后一个命令和总计:

 0.000894541      0.000894541 Starting
 0.001306122      0.002200663 ((i=3))
 0.001929397      0.004130060 ((i--))
 0.103035812      0.107165872 sleep .1
 0.000875613      0.108041485 ((1))
 0.000813872      0.108855357 ((i--))
 0.104954517      0.213809874 sleep .1
 0.000900617      0.214710491 ((1))
 0.000842159      0.215552650 ((i--))
 0.104846890      0.320399540 sleep .1
 0.000899082      0.321298622 ((1))
 0.000811708      0.322110330 ((i--))
 0.000879455      0.322989785 elapCalc2
 0.322989785 first total
 0.000906692      0.323896477 elapShowTotal \e[1mfirst total\e[0m
 0.000820089      0.324716566 ((i=2))
 0.000773782      0.325490348 ((i--))
 0.024752613      0.350242961 tar -cf /tmp/test.tar -C / bin
 0.596199363      0.946442324 gzip /tmp/test.tar
 0.003007128      0.949449452 rm /tmp/test.tar.gz
 0.000791452      0.950240904 ((1))
 0.000779371      0.951020275 ((i--))
 0.030519702      0.981539977 tar -cf /tmp/test.tar -C / bin
 0.584155405      1.565695382 gzip /tmp/test.tar
 0.003058674      1.568754056 rm /tmp/test.tar.gz
 0.000955093      1.569709149 ((1))
 0.000919964      1.570629113 ((i--))
 1.571516599 total time
 0.001723708      1.572352821 EXIT
Run Code Online (Sandbox Code Playgroud)

运用 strace

是的,strace可以做这个工作:

strace -q -f -s 10 -ttt sample-script 2>sample-script-strace.log
Run Code Online (Sandbox Code Playgroud)

但是可能会有很多东西!

wc sample-script-strace.log
    6925  57637 586518 sample-script-strace.log
Run Code Online (Sandbox Code Playgroud)

使用更受限制的命令:

strace -f -s 10 -ttt -eopen,access,read,write ./sample-script 2>sample-script-strace.log
Run Code Online (Sandbox Code Playgroud)

将转储更轻的日志:

  4519  36695 374453 sample-script-strace.log
Run Code Online (Sandbox Code Playgroud)

根据您要搜索的内容,您可能会受到更多限制:

 strace -f -s 10 -ttt -eaccess,open ./sample-script 2>&1 | wc
  189    1451   13682
Run Code Online (Sandbox Code Playgroud)

阅读它们会有点困难:

{
    read -a first
    first=${first//.}
    last=$first
    while read tim line;do
        crt=000000000$((${tim//.}-last))
        ctot=000000000$((${tim//.}-first))
        printf "%9.6f %9.6f %s\n" ${crt:0:${#crt}-6}.${crt:${#crt}-6} \
            ${ctot:0:${#ctot}-6}.${ctot:${#ctot}-6} "$line"
        last=${tim//.}
      done
  } < <(
    sed </tmp/sample-script.strace -e '
        s/^ *//;
        s/^\[[^]]*\] *//;
        /^[0-9]\{4\}/!d
  ')

 0.000110  0.000110 open("/lib/x86_64-linux-gnu/libtinfo.so.5", O_RDONLY) = 4
 0.000132  0.000242 open("/lib/x86_64-linux-gnu/libdl.so.2", O_RDONLY) = 4
 0.000121  0.000363 open("/lib/x86_64-linux-gnu/libc.so.6", O_RDONLY) = 4
 0.000462  0.000825 open("/dev/tty", O_RDWR|O_NONBLOCK) = 4
 0.000147  0.000972 open("/usr/lib/locale/locale-archive", O_RDONLY) = 4
 ...
 0.000793  1.551331 open("/etc/ld.so.cache", O_RDONLY) = 4
 0.000127  1.551458 open("/lib/x86_64-linux-gnu/libc.so.6", O_RDONLY) = 4
 0.000545  1.552003 open("/usr/lib/locale/locale-archive", O_RDONLY) = 4
 0.000439  1.552442 --- SIGCHLD (Child exited) @ 0 (0) ---
Run Code Online (Sandbox Code Playgroud)

原始的bash脚本在这个中并不那么容易理解......

使用script,scriptreplay定时文件

作为BSD Utils的一部分,script(和scriptreplay)是一个非常古老的工具,可用于配置bash,占用空间非常小.

script -t script.log 2>script.tim -c 'bash -x -c "
    for ((i=3;i--;));do sleep .1;done

    for ((i=2;i--;)) ;do
        tar -cf /tmp/test.tar -C / bin
        gzip /tmp/test.tar
        rm /tmp/test.tar.gz
    done
"'
Run Code Online (Sandbox Code Playgroud)

会产生:

Script started on Fri Mar 25 08:29:37 2016
+ (( i=3 ))
+ (( i-- ))
+ sleep .1
+ (( 1 ))
+ (( i-- ))
+ sleep .1
+ (( 1 ))
+ (( i-- ))
+ sleep .1
+ (( 1 ))
+ (( i-- ))
+ (( i=2 ))
+ (( i-- ))
+ tar -cf /tmp/test.tar -C / bin
+ gzip /tmp/test.tar
+ rm /tmp/test.tar.gz
+ (( 1 ))
+ (( i-- ))
+ tar -cf /tmp/test.tar -C / bin
+ gzip /tmp/test.tar
+ rm /tmp/test.tar.gz
+ (( 1 ))
+ (( i-- ))
Script done on Fri Mar 25 08:29:39 2016
Run Code Online (Sandbox Code Playgroud)

并生成两个文件:

ls -l script.*
-rw-r--r-- 1 user user 450 Mar 25 08:29 script.log
-rw-r--r-- 1 user user 177 Mar 25 08:29 script.tim
Run Code Online (Sandbox Code Playgroud)

文件script.log包含所有跟踪并且script.tim计时文件:

head -n 4 script.*
==> script.log <==
Script started on Fri Mar 25 08:29:37 2016
+ (( i=3 ))
+ (( i-- ))
+ sleep .1

==> script.tim <==
0.435331 11
0.000033 2
0.000024 11
0.000010 2
Run Code Online (Sandbox Code Playgroud)

您可以看到日志文件的第一行和最后一行的总执行时间和/或总结时间文件中的时间:

head -n1 script.log ;tail -n1 script.log 
Script started on Fri Mar 25 08:29:37 2016
Script done on Fri Mar 25 08:29:39 2016

sed < script.tim  's/ .*$//;H;${x;s/\n/+/g;s/^\+//;p};d' | bc -l
2.249755
Run Code Online (Sandbox Code Playgroud)

在计时文件中,第二个值是相应日志文件中的下一个字节数.这使您可以选择使用加速因子重播日志文件:

scriptreplay script.{tim,log}
Run Code Online (Sandbox Code Playgroud)

要么

scriptreplay script.{tim,log} 5
Run Code Online (Sandbox Code Playgroud)

要么

 scriptreplay script.{tim,log} .2
Run Code Online (Sandbox Code Playgroud)

并排显示时间和命令也有点复杂:

exec 4<script.log
read -u 4 line
echo $line ;while read tim char;do
    read -u 4 -N $char -r -s line
    echo $tim $line
  done < script.tim &&
while read -u 4 line;do
    echo $line
done;exec 4<&-
Script started on Fri Mar 25 08:28:51 2016
0.558012 + (( i=3 ))
0.000053 
0.000176 + (( i-- ))
0.000015 
0.000059 + sleep .1
0.000015 
 + sleep .1) + (( 1 ))
 + sleep .1) + (( 1 ))
 + tar -cf /tmp/test.tar -C / bin
0.035024 + gzip /tmp/test.tar
0.793846 + rm /tmp/test.tar.gz
 + tar -cf /tmp/test.tar -C / bin
0.024971 + gzip /tmp/test.tar
0.729062 + rm /tmp/test.tar.gz
 + (( i-- )) + (( 1 ))
Script done on Fri Mar 25 08:28:53 2016
Run Code Online (Sandbox Code Playgroud)

测试和结论

为了进行测试,我在bash complex hello world下载了第二个样本,这个脚本大约需要0.72秒才能在我的主机上完成.

我在脚本的顶部添加了以下内容之一:

并比较执行时间(在我的主机上):

  • 直接 0.72秒
  • elap.bash 13.18秒
  • 设定+日期@ PS4 54.61秒
  • 设置+ 1分叉 1.45秒
  • 脚本和计时文件 2.19秒
  • strace 4.47秒

输出

  • elap.bash功能

         0.000950277      0.000950277 Starting
         0.007618964      0.008569241 eval "BUNCHS=(" $(perl <<EOF | gunzi
         0.005259953      0.013829194 BUNCHS=("2411 1115 -13 15 33 -3 15 1
         0.010945070      0.024774264 MKey="V922/G/,2:"
         0.001050990      0.025825254 export RotString=""
         0.004724348      0.030549602 initRotString
         0.001322184      0.031871786 for bunch in "${BUNCHS[@]}"
         0.000768893      0.032640679 out=""
         0.001008242      0.033648921 bunchArray=($bunch)
         0.000741095      0.034390016 ((k=0))
    
    Run Code Online (Sandbox Code Playgroud)
  • set -xPS4

    ++ 1388598366.536099290  perl
    ++ 1388598366.536169132  gunzip
    + 1388598366.552794757   eval 'BUNCHS=(' '"2411' 1115 -13 15 33 -3 15 1
    ++ 1388598366.555001983  BUNCHS=("2411 1115 -13 15 33 -3 15 13111 -6 1
    + 1388598366.557551018   MKey=V922/G/,2:
    + 1388598366.558316839   export RotString=
    + 1388598366.559083848   RotString=
    + 1388598366.560165147   initRotString
    + 1388598366.560942633   local _i _char
    + 1388598366.561706988   RotString=
    
    Run Code Online (Sandbox Code Playgroud)
  • by set -xinitial fork to long exec命令(以及我的第二个paste示例脚本)

     0.000000000  0.000000000    ++ perl
     0.008141159  0.008141159    ++ gunzip
     0.000007822  0.008148981    + eval 'BUNCHS=(' '"2411' 1115 -13 15 33 -3 
     0.000006216  0.008155197    ++ BUNCHS=("2411 1115 -13 15 33 -3 15 13111 
     0.000006216  0.008161413    + MKey=V922/G/,2:
     0.000006076  0.008167489    + export RotString=
     0.000006007  0.008173496    + RotString=
     0.000006006  0.008179502    + initRotString
     0.000005937  0.008185439    + local _i _char
     0.000006006  0.008191445    + RotString=
    
    Run Code Online (Sandbox Code Playgroud)
  • 通过 strace

     0.000213  0.000213 brk(0)                = 0x17b6000
     0.000044  0.000257 access("/etc/ld.so.nohwcap", F_OK) = -1 ENOENT (No such file or directory)
     0.000047  0.000304 mmap(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7faf1c0dc000
     0.000040  0.000344 access("/etc/ld.so.preload", R_OK) = -1 ENOENT (No such file or directory)
     0.000040  0.000384 open("/etc/ld.so.cache", O_RDONLY) = 4
     ...
     0.000024  4.425049 close(10)             = 0
     0.000042  4.425091 rt_sigprocmask(SIG_BLOCK, NULL, [], 8) = 0
     0.000028  4.425119 read(255, "", 4409)   = 0
     0.000058  4.425177 exit_group(0)         = ?
    
    Run Code Online (Sandbox Code Playgroud)
  • 通过 script

    Le script a débuté sur ven 25 mar 2016 09:18:35 CET
    0.667160 ++ gunzip
    0.000025 
    0.000948 ++ perl
    0.000011 
    0.005338 + eval 'BUNCHS=(' '"2411' 1115 -13 15 33 -3 15 13111 -6 1 111 4
    0.000044 1223 15 3311 121121 17 3311 121121 1223 3311 121121 17 3311 121
    0.000175 ++ BUNCHS=("2411 1115 -13 15 33 -3 15 13111 -6 15 1114 15 12211
    0.000029 1 1321 12211 412 21211 33 21211 -2 15 2311 11121 232 121111 122
    0.000023 4 3311 121121 12221 3311 121121 12221 3311 121121 1313 -6 15 33
    
    Run Code Online (Sandbox Code Playgroud)

结论

好!如果我的纯bash在每个命令上分配的速度快,那么我的纯bash意味着对每个命令进行一些操作.

专用于记录和存储的独立过程的方式显然更有效.

strace 是一种有趣的方式,更详细,但难以阅读.

script,scriptreplay和加速因子也是非常好的,不一样的精度,因为这是基于控制台交换而不是流程执行,但非常轻和高效(不同目标,不相同的用法).

最后,我认为更高效,可读性和性能更好set + 1 fork,这是第一个答案,但很好,根据具体情况,我会使用某些时间strace和/或script.

  • 这发布在[Unix&Linux - 如何调整时间命令以测量程序耗用时间](http://unix.stackexchange.com/a/60434/27653) (2认同)
  • “时间”部分提供了很多信息,使您明白了叉子是没有什么可打喷嚏的(实际上是完全统治了许多脚本)。+1是一个很好的答案(如果需要长时间考虑)。也许将来您应该考虑发布单独的答案 (2认同)

Fri*_*ner 17

跟踪系统调用通常很有帮助

strace -c -f ./script.sh
Run Code Online (Sandbox Code Playgroud)

从手册:

-c计算每个系统调用的时间,调用和错误,并报告程序退出的摘要.

-f跟踪子进程...

这不是你想要的,以及面向行的探查器会向你显示的内容,但它通常有助于找到热点.


小智 5

您可以查看trap具有DEBUG条件的命令.有一种方法可以设置要与命令一起执行的命令.请参阅答案的注释.