說明:在進行一些效能測試的時候,有時候我們希望能計算一個程式啟動並執行時間,有時候可能會自己寫一個shell指令碼方便進行一些效能測試的控制(比如希望能運行N次取平均值等),總之,這其中有一個需求可能就是擷取一個時間戳記或時間差。
1. Linux shell擷取時間的相關命令
time命令:擷取一個程式的執行時間,可以擷取到實際已耗用時間以及程式在使用者態和核心態分別的時間,大部分的效能測試,可能只需要關注實際時間。
time命令的使用就很簡單了,在原有的要啟動並執行程式(可執行檔、指令碼等任何可執行程式)之前加上time即可。
問題一:time命令的常用選項
使用time,常用的選項是:-f和-p。其中-f後面指定一個格式串,控制time的輸出的格式,預設的time輸出是real、user、sys三行以xmxxx.xxxs的格式輸出,通過-f可以控制。
-p選項也是格式,表示使用posix標準的格式,主要的區別是顯示的時間都是以s為單位的,具體關于格式的問題參考man time的協助內容就知道了。
PS:-f選項沒法工作?弄不清楚為何-f和-o等選項都無法工作,知道的請補充。(-p是工作的)
另一種控制格式的方法是設定TIMEFORMAT環境變數,具體參考man time可以知道這些格式控制符分別表示什麼。舉例如下:
[plain] view plaincopyprint?
- #time pwd
- /home/sgeng2
-
- real 0m0.000s
- user 0m0.000s
- sys 0m0.000s
- #export TIMEFORMAT="real time %E, user time %U,sys time %S"
- #time pwd
- /home/sgeng2
- real time 0.000, user time 0.000,sys time 0.000
- #time -p pwd
- /home/sgeng2
- real 0.00
- user 0.00
- sys 0.00
- #
#time pwd/home/sgeng2real0m0.000suser0m0.000ssys0m0.000s#export TIMEFORMAT="real time %E, user time %U,sys time %S"#time pwd/home/sgeng2real time 0.000, user time 0.000,sys time 0.000#time -p pwd/home/sgeng2real 0.00user 0.00sys 0.00#
PS:很奇怪,感覺time的有些格式和上面的選項一樣,好像不完全工作,總之,關係不大,格式並不重要,一般使用-p以秒作為單位就足夠了。
問題二:time命令的輸出的問題
上面已經說到,好像-o選項並不工作,那麼,我們就只能自己想辦法了。有時候寫指令碼,就希望把time的結果輸出到檔案中,然後可能會根據time的輸出進行一些處理,比如提取出real時間等。顯然,大家能想到的是重新導向了,至於重新導向的使用這裡顯然不準備討論(太複雜),只是提示一點:time命令的輸出結果是輸出到stderr的,不是stdout,所以進行重新導向的時候要注意了。看懂下面的例子基本就能理解了:
[plain] view plaincopyprint?
- #time pwd
- /home/sgeng2
-
- real 0m0.000s
- user 0m0.000s
- sys 0m0.000s
- #time pwd > out.txt
-
- real 0m0.000s
- user 0m0.000s
- sys 0m0.000s
- #cat out.txt
- /home/sgeng2
- #time pwd 2> out.txt
- /home/sgeng2
-
- real 0m0.000s
- user 0m0.000s
- sys 0m0.000s
- #cat out.txt
- #(time pwd) 2> out.txt
- /home/sgeng2
- #cat out.txt
-
- real 0m0.000s
- user 0m0.000s
- sys 0m0.000s
- #(time pwd) >& out.txt
- #cat out.txt
- /home/sgeng2
-
- real 0m0.000s
- user 0m0.000s
- sys 0m0.000s
- #
#time pwd/home/sgeng2real0m0.000suser0m0.000ssys0m0.000s#time pwd > out.txt real0m0.000suser0m0.000ssys0m0.000s#cat out.txt /home/sgeng2#time pwd 2> out.txt /home/sgeng2real0m0.000suser0m0.000ssys0m0.000s#cat out.txt #(time pwd) 2> out.txt /home/sgeng2#cat out.txt real0m0.000suser0m0.000ssys0m0.000s#(time pwd) >& out.txt #cat out.txt /home/sgeng2real0m0.000suser0m0.000ssys0m0.000s#
PS:這裡更多的是涉及到的和重新導向相關的內容,所以不會詳細分析每一個例子。說明的是注意time pwd 2> out.txt和(time pwd) 2> out.txt的區別,前面一個的含義是把pwd的結果stderr重新導向到out.txt,相當於"time (pwd 2> out.txt)"的結果。
date命令:
關於date命令的使用,百度一把一大堆,就不重複了,例如可以參考:http://xingfujie.blog.51cto.com/2791569/637223
這裡只說明一下幾個常見的問題:
問題一:date的%s和%N
date中有很多控制格式的,其中%s是擷取目前時間距離1970-01-01 00:00:00 UTC的時間差。date的其它很多格式控制都是控制目前時間的輸出格式而已,比如只輸出時分秒,只輸出年月日等等,其中%N也是這一類,%N輸出的是目前時間的納秒部分,由於date並沒有毫秒等層級的輸出,所以在秒以下的內容都屬於納秒部分。所以從這個角度說,date是可以很精確的,可以達到納秒層級。
問題二:擷取一個時間戳記
有時候會使用時間戳,或者隨機數,UUID這樣的東西,百度一下也有相關文章(比如搜尋”shell date隨機數“等)。一般來說,可以用%s和%N組合的方式就沒問題,同一秒內,兩次運行%N肯定不會一樣,所以%s和%N組合能得到一個唯一數。
[plain] view plaincopyprint?
- #date +%s.%N
- 1337435374.969263560
- #date +%s+%N
- 1337435377+310281496
- #date +%s_%N
- 1337435381_209334510
- #date +%s_%N
- 1337435383_169263078
- #date +%s_%N
- 1337435383_830009679
- #
#date +%s.%N1337435374.969263560#date +%s+%N1337435377+310281496#date +%s_%N1337435381_209334510#date +%s_%N1337435383_169263078#date +%s_%N1337435383_830009679#
PS:有時候可能希望用一個”唯一“的東西來對檔案命名等,就可以加上時間戳記了。
2. Linux shell擷取時間差(使用date命令)
至於使用time命令,其本身就是擷取一個時間差,但是顯然,time只適用於一些簡單的情況,因為後面的參數是可以執行的內容,有時候可能需要執行多條命令,用time就比較麻煩。
(1) 秒為單位
date擷取的是”目前時間“,顯然,兩次運行date就可以得到一個時間差,理論上,可以使用很多格式來表示date的輸出,從而計算時間差,但是,顯然,最直接的方式就是使用%s了,這樣直接就可以計算出一個時間差,不需要那麼複雜的shell字串處理了。如下:
[plain] view plaincopyprint?
- #start=$(date +%s) && sleep 2 && end=$(date +%s) && echo $(( $end - $start ))
- 2
- #start=$(date +%s) && sleep 3 && end=$(date +%s) && echo $(( $end - $start ))
- 3
- #
#start=$(date +%s) && sleep 2 && end=$(date +%s) && echo $(( $end - $start ))2#start=$(date +%s) && sleep 3 && end=$(date +%s) && echo $(( $end - $start ))3#
當然,這裡是在terminal中測試的,所以用&&連續執行這些命令,對於指令碼,一行一行的寫就很好了。。。。
[plain] view plaincopyprint?
- start=$(date +%s)
- ...what to do for timing...
- end=$(date +%s)
- time=$(( $end - $start ))
- echo $time
start=$(date +%s)...what to do for timing...end=$(date +%s)time=$(( $end - $start ))echo $time
(2) ms為單位
更多的效能測試等場合擷取時間差,有可能希望精確到ms。事實上,使用date是可以達到ms的。直接上代碼吧。
[plain] view plaincopyprint?
- #! /bin/bash
- #filename: test.sh
-
-
- # arg1=start, arg2=end, format: %s.%N
- function getTiming() {
- start=$1
- end=$2
-
- start_s=$(echo $start | cut -d '.' -f 1)
- start_ns=$(echo $start | cut -d '.' -f 2)
- end_s=$(echo $end | cut -d '.' -f 1)
- end_ns=$(echo $end | cut -d '.' -f 2)
-
-
- # for debug..
- # echo $start
- # echo $end
-
-
- time=$(( ( 10#$end_s - 10#$start_s ) * 1000 + ( 10#$end_ns / 1000000 - 10#$start_ns / 1000000 ) ))
-
-
- echo "$time ms"
- }
-
-
-
-
- echo "This is only a test to get a ms level time duration..."
- start=$(date +%s.%N)
- ls >& /dev/null # hey, be quite, do not output to console....
- end=$(date +%s.%N)
-
-
- getTiming $start $end
#! /bin/bash#filename: test.sh# arg1=start, arg2=end, format: %s.%Nfunction getTiming() { start=$1 end=$2 start_s=$(echo $start | cut -d '.' -f 1) start_ns=$(echo $start | cut -d '.' -f 2) end_s=$(echo $end | cut -d '.' -f 1) end_ns=$(echo $end | cut -d '.' -f 2)# for debug..# echo $start# echo $end time=$(( ( 10#$end_s - 10#$start_s ) * 1000 + ( 10#$end_ns / 1000000 - 10#$start_ns / 1000000 ) )) echo "$time ms"}echo "This is only a test to get a ms level time duration..."start=$(date +%s.%N)ls >& /dev/null # hey, be quite, do not output to console....end=$(date +%s.%N)getTiming $start $end
PS:這個代碼是一個簡單的測試,可以擷取到ls命令執行的時間,相信其執行時間已經夠短了,如果你需要擷取的時間差在ms以下,相信你不會使用shell了,嘿嘿,自然要靠C去弄了。
結果如下:
[html] view plaincopyprint?
- #./test.sh
- This is only a test to get a ms level time duration...
- 3 ms
- #./test.sh
- This is only a test to get a ms level time duration...
- 2 ms
- #./test.sh
- This is only a test to get a ms level time duration...
- 2 ms
- #
#./test.sh This is only a test to get a ms level time duration...3 ms#./test.sh This is only a test to get a ms level time duration...2 ms#./test.sh This is only a test to get a ms level time duration...2 ms#
還滿意吧,能擷取到這麼短的時間。當然,理論上可以擷取到以ns為單位的時間差,但是,個人覺得用shell去弄這麼小的時間差,你覺得準確麼。。。
說明:
上面的代碼的思路,其實很簡單了,%s為距離標準時間的秒數,%N為目前時間的秒以下的部分,那麼顯然,%s和%N就表示了目前時間的完整時間戳記,兩個時間戳記就差值就是時間差,問題就是如何處理的問題,大概就是:先使用%s.%N的格式擷取到start和end時間,然後利用cut命令從start和end中擷取到“.“前面的秒的部分和後面的納秒的部分(說明:這裡的在%s和%N之間插入點,只是作為分隔的作用,任何可能的字元都是可以的,只要能被cut分開就行);然後,用秒的部分相減,得到秒的差值,轉換為毫秒的差值;然後,把納秒的部分轉換為毫秒之後求差值(可能為負數);最後,兩個差值相加就是真正的以毫秒為單位的差值了。很容易理解,關鍵是cut的使用,對於shell高手來說,應該有很多方法可以對字串提取,但是對於我這樣的非shell高手,要自己用awk或sed什麼的寫一個提取的正則,還是很有難度,還好找到了cut命令,能很容易的對這種字串進行提取。所以:這裡的方法僅供參考。。。
關於代碼中的“10#”,這是表示後面的數是10進位的數字,之所以需要這個是因為這裡的納秒的前面是以0開頭的,shell中好像以0開頭會預設認為是八進位,導致運行報錯,總之,百度一下就找到了原因,這裡就索性把所有的數字都加上了10#,表示都是10進位。