歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
您现在的位置: Linux教程網 >> UnixLinux >  >> Linux編程 >> SHELL編程

一個Linux Shell程序的性能優化技巧

本文Shell 程序運行環境:

    * 程序運行環境 Redhat Linux As3* GNU bash, version 2.05b.0(1)-release (i386-redhat-linux-gnu)* 代碼清單:shellcode.txt

問題描述:有一個普通的通話話單文件(包括"計費號碼","主叫號碼","被叫號碼","開始時間","結束時間","時長","費用"等其它字段),要求根據另外一個號段配置文件(由"號段下限"和"號段上限"兩個字段組成)將此話單文件進行分揀過慮。

分揀規則:如果通話話單文件中的"計費號碼"位於號段文件的某個號段內,則將此條記錄計入結果文件 1,否則計入結果文件 2。

通話話單文件樣例:

9013320003901332000399181280252006081416342020060814163450302000010059926645208992664520899181880652006081416341520060814163545903000010059934877207993487720799369720032006081416362020060814163930190500001005............

號段配置文件樣例:

9013305000,90133279999013767000,90137689999923670000,99236799999928998000,99289999999932310000,99323199999932333400,99323335999936034000,99360369999936084000,99360849999998537000,99985379999998620000,99986299999998690000,9998699999

例如:

對於通話話單文件的第一條記錄中的"計費號碼"為 9013320000,此號碼正好屬於號段配置文件的第一個號段 9013305000,9013327999中,即:條件 9013305000<= 9013320000 <=9013327999 成立,所以應該將通話話單文件的第一條記錄計入結果文件 1 中;對於通話話單文件中的第二條記錄的"計費號碼"為 9926645208 它不屬於號段文件中的任何一個段,所以應該將通話話單的第二條記錄計入結果文件 2 中。

對於這樣一個簡單的問題首先想到的解決方法為:

解決方法1:

寫一個雙重循環,外層循環為逐條讀取"通話話單文件"並獲取每條記錄的第一個字段的值"計費號碼",內層循環:根據外層循環獲得的"計費號碼"在"號段文件"中循環比較,判斷此號碼是否屬於相應號段。

程序代碼如下(省略了文件存在性判斷等語句):

while read fdoorg="$(eXPr substr ${f} 1 10)"   #取得"計費號碼"存入變量org中while read numsegdonglow="$(expr substr ${numseg} 1 10 )"   #將號段下限存入變量nglowngtop="$(expr substr ${numseg} 12 10 )"  #將號段上限存入變量ngtopif [ "$org" \> "$nglow"  -a "$org" \< $ngtop ]#判斷"計費號碼"是否在此號段內thenecho "${f}" >> ./resultfile1.cdr #如果在此號段內,將此記錄計入結果文件1中else echo "${f}" >> ./resultfile2.cdr #如果不在此號段內,將此記錄計入結果文件2中fidone < ./numseg.txtdone < ./rttest.txt

解決方法1 對於號段文件和通話話單的記錄數都比較少的情況下基本可以完成工作,但是當兩個文件的記錄數較多(例如號段文件>50條,話單文件> 10000條)的時候,這種方法就會花費幾個小時甚至幾天的時間才能得出處理結果。此腳本程序執行慢的原因是對第二個循環內的比較運算只用了最簡單的順序比較方法,所以當號段文件的記錄增多的時候,腳本的執行速度會急劇下降。

解決方法2:

將內層循環的逐個比較的方法改為二分查找法進行判斷,程序代碼如下:

#!/bin/bash#Author Xsh  date:08-15-2006#程序中使用了二分查找法進行號碼的范圍判斷#為突出重點,省略了文件存在性判斷和異常捕獲以及幫助提示等程序語句#程序的工作目錄為當前目錄

echo "Time:$(date)==>Strat to processing........." #顯示程序開始運行時間while read numsegdotmplow="${tmplow} $(expr substr ${numseg} 1 10 & >/dev/null ) "tmptop="${tmptop} $(expr substr ${numseg} 12 10 & >/dev/null ) "done < ./numseg.txt#讀取號段文件,下限號段存入變量tmplow,上限號段存入變量tmptoparr_lownug=(${tmplow}) #將下限號段存入數組arr_lownugarr_topnug=(${tmptop}) #將上限號段存入數組arr_topnug

#定義函數checknum(),輸入參數為需要檢查的"計費號碼",輸出參數為0或者1#若checknum()輸出為0 表示"計費號碼" 不在號段文件的所有號段范圍內#若checknum()輸出為1 表示"計費號碼" 在號段文件的所有號段范圍內# checknum()函數中用二分搜索法進行號碼的判斷checknum(){thisnum=$1ckresult=0lowflag=0topflag=$(expr ${#arr_lownug[*]} - 1 )  #標注1MaxIndex=$(expr ${#arr_topnug[*]} - 1 ) #標注2midflag=0midflag=$(expr ${topflag} / 2 )  #標注3if [ "${thisnum}" \< "${arr_lownug[0]}" -o "${thisnum}" \>"${arr_topnug[${MaxIndex}]}"  ]thenreturn 0elsewhile [ "$lowflag" != "$midflag" ]doif[ "$thisnum" \> "${arr_lownug[${midflag}]}" -o "$thisnum" == \"${arr_lownug[${midflag}]}" ]thenlowflag=${midflag}midflag=$(expr `expr ${topflag} + ${lowflag}` / 2 ) #標注4elif["$thisnum"\<"${arr_lownug[${midflag}]}" -o "$thisnum" == \"${arr_lownug[${midflag}]}" ] thentopflag=${midflag}midflag=$(expr `expr ${topflag} + ${lowflag}` / 2 ) #標注5elseecho "Error!"   fidone if [ "$thisnum" \< "${arr_topnug[${lowflag}]}" -o "$thisnum" == \"${arr_topnug[${lowflag}]}" ]thenreturn 1else return 0 fifi}#函數定義完畢

while read fdoorg="$(expr substr ${f} 1 10)" #標注6checknum ${org}returnval=$?if [ "$returnval" == "1"  ]then echo "${f}" >> ./Match_result.cdr  #將匹配的記錄存入結果文件1elseecho "${f}" >> ./NoMatch_result.cdr #將不匹配的記錄存入結果文件2fidone < ./rttest.txt echo "Time:$(date) ==> Proccess is end! "exit 0;

將以上程序投入運行,號段文件有 71行記錄,需要分揀的通話話單文件17 萬條左右通過觀察發現此方法的執行效率確實比解決方法1 提高了很多,但是仍需要數小時的時間才能執行完畢。我另外寫了一個同樣功能的C語言程序,執行同樣的測試數據得出結果僅需要10~15秒的時間!shell 程序確實要比同樣功能的C程序慢一些,但是目前的情況是用C程序處理只需要幾秒鐘時間, 而Shell程序確需要數小時!在用此Shell程序處理的時候我用Top命令看了一些系統資源的消耗發現CPU、內存以及IO的占用都極小,說明系統資源很充足,不是系統的問題造成了程序處理速度慢。問題出在哪了呢?

我用命名ps -ef 觀察系統進程的運行情況,發現每過幾秒種就會有一個expr進程產生,這個進程運行很短的時間就消失了(不仔細觀察可能都看不到有expr這個進程產生)。這時候我覺得我好像發現程序運行慢的原因了:程序的幾個循環裡面都有用expr進行計算的語句,expr屬於Shell外部命令,所以每次運算都要產生一個 expr進程,而程序的運行種最消耗時間的除了IO操作外就數產生新進程操作了,於是我決定把用expr進行計算的地方都盡量修改為用shell的內部命令進行計算。程序變成了下面的樣子(標注1~標注6為修改過的地方):

程序代碼如下:

―――――――――――――――――――――――――――――――――――――――#!/bin/bash#Author Xsh  date:08-15-2006echo "Time:$(date)==>Strat to processing........." #顯示程序開始運行時間while read numseg do  tmplow="${tmplow} $(expr substr ${numseg} 1 10 & >/dev/null ) "   tmptop="${tmptop} $(expr substr ${numseg} 12 10 & >/dev/null ) "done < ./numseg.txt #循環讀取號段文件下限號段存入變量tmplow,上限號段存入變量tmptop

arr_lownug=(${tmplow}) #將下限號段存入數組arr_lownugarr_topnug=(${tmptop}) #將上限號段存入數組arr_topnug

#定義函數checknum(),輸入參數為需要檢查的"計費號碼",輸出參數為0或者1#函數checknum()輸出為0 表示"計費號碼" 不在號段文件的所有號段范圍內#函數checknum()輸出為1 表示"計費號碼" 在號段文件的所有號段范圍內#函數checknum()中用二分搜索法進行號碼的判斷checknum(){ #函數定義開始thisnum=$1ckresult=0lowflag=0topflag=$((${#arr_lownug[*]} - 1 ))   #標注1MaxIndex=$((${#arr_topnug[*]} - 1 ))  #標注2midflag=0midflag=$((${topflag} / 2 ))       #標注3if [ "${thisnum}" \< "${arr_lownug[0]}" -o "${thisnum}" \> "${arr_topnug[${MaxIndex}]}" ]thenreturn 0else

while [ "$lowflag" != "$midflag" ]doif ["$thisnum"\> "${arr_lownug[${midflag}]}" -o "$thisnum" == \"${arr_lownug[${midflag}]}"]thenlowflag=${midflag}midflag=$(( $((${topflag} + ${lowflag})) / 2 )) #標注4elif["$thisnum"\<"${arr_lownug[${midflag}]}" -o "$thisnum" == \"${arr_lownug[${midflag}]}"] then    topflag=${midflag}midflag=$(($((${topflag} + ${lowflag})) / 2 )) #標注5elseecho "Error!"   fidone if ["$thisnum" \< "${arr_topnug[${lowflag}]}" -o "$thisnum" == \"${arr_topnug[${lowflag}]}"]thenreturn 1else return 0 fifi}#函數定義結束

while read fdoorg="${f:0:10}"  #標注6checknum ${org}returnval=$?if [ "$returnval" == "1"  ]then echo "${f}" >> ./Match_result.cdr  #將匹配的記錄存入結果文件1elseecho "${f}" >> ./NoMatch_result.cdr #將不匹配的記錄存入結果文件2fidone < ./rttest.txt echo "Time:$(date) ==> Proccess is end! "exit 0;

將修改過的程序進行運行,很快就得出了結果,總的運行時間沒有超過8分鐘。此時這個程序的運行效率已經基本可以讓人接受了。同樣的一個問題由於改進了程序算法和充分利用了LinuxShell的內置運算符,將程序的運行時間大大的縮短。當然此程序還有可以改進的地方,對此文感興趣的讀者可以對此程序做進一步優化。




Copyright © Linux教程網 All Rights Reserved