1. gzyueqian
      18529173453
      首頁 > 新聞中心 > > 正文

      一個Shell 程序的性能優(yōu)化

      更新時間: 2007-06-05 10:10:27來源: 粵嵌教育瀏覽量:1591

        編寫 Linux Shell 腳本程序不要僅限于完成基本的程序功能,認真的分析 Shell 腳本并找出優(yōu)化的方法對個人能力的提高以及對腳本程序的質(zhì)量改善都有重要的意義,希望讀者能從本文中獲得許多實用的 Shell 程序方法。
      本文 Shell 程序運行環(huán)境:
        程序運行環(huán)境 Redhat Linux As3
        GNU bash, version 2.05b.0(1)-release (i386-redhat-linux-gnu)
        代碼清單:shellcode.txt

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

        分揀規(guī)則:如果通話話單文件中的"計費號碼"位于號段文件的某個號段內(nèi),則將此條記錄計入結(jié)果文件 1,否則計入結(jié)果文件 2。

        通話話單文件樣例:

        9013320003|9013320003|9918128025|20060814163420|20060814163450|30|20|00|01|005
        9926645208|9926645208|9918188065|20060814163415|20060814163545|90|30|00|01|005
        9934877207|9934877207|9936972003|20060814163620|20060814163930|190|50|00|01|005
        ......
        ......

        號段配置文件樣例:
        9013305000,9013327999
        9013767000,9013768999
        9923670000,9923679999
        9928998000,9928999999
        9932310000,9932319999
        9932333400,9932333599
        9936034000,9936036999
        9936084000,9936084999
        9998537000,9998537999
        9998620000,9998629999
        9998690000,9998699999

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

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

        寫一個雙重循環(huán),外層循環(huán)為逐條讀取"通話話單文件"并獲取每條記錄的個字段的值"計費號碼",內(nèi)層循環(huán):根據(jù)外層循環(huán)獲得的"計費號碼"在"號段文件"中循環(huán)比較,判斷此號碼是否屬于相應(yīng)號段。

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

        while read f
        do
        org="$(expr substr ${f} 1 10)"   #取得"計費號碼"存入變量org中
        while read numseg
        do
        nglow="$(expr substr ${numseg} 1 10 )"   #將號段下限存入變量nglow
        ngtop="$(expr substr ${numseg} 12 10 )"  #將號段上限存入變量ngtop
        if [ "$org" \> "$nglow"  -a "$org" \< $ngtop ]
        #判斷"計費號碼"是否在此號段內(nèi)
        then
        echo "${f}" >> ./resultfile1.cdr #如果在此號段內(nèi),將此記錄計入結(jié)果文件1中
        else
        echo "${f}" >> ./resultfile2.cdr #如果不在此號段內(nèi),將此記錄計入結(jié)果文件2中
        fi
        done < ./numseg.txt
        done < ./rttest.txt

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

        解決方法2:
        將內(nèi)層循環(huán)的逐個比較的方法改為二分查找法進行判斷,程序代碼如下:

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

        echo "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}) #將下限號段存入數(shù)組arr_lownug
        arr_topnug=(${tmptop}) #將上限號段存入數(shù)組arr_topnug
        
        #定義函數(shù)checknum(),輸入?yún)?shù)為需要檢查的"計費號碼",輸出參數(shù)為0或者1
        #若checknum()輸出為0 表示"計費號碼" 不在號段文件的所有號段范圍內(nèi)
        #若checknum()輸出為1 表示"計費號碼" 在號段文件的所有號段范圍內(nèi)
        # checknum()函數(shù)中用二分搜索法進行號碼的判斷
        checknum(){
        thisnum=$1
        ckresult=0
        lowflag=0
        topflag=$(expr ${#arr_lownug[*]} - 1 )  #標(biāo)注1
        MaxIndex=$(expr ${#arr_topnug[*]} - 1 ) #標(biāo)注2
        midflag=0
        midflag=$(expr ${topflag} / 2 )  #標(biāo)注3
        if [ "${thisnum}" \< "${arr_lownug[0]}" -o "${thisnum}" \>
        "${arr_topnug[${MaxIndex}]}"  ]
        then
        return 0
        else
        while [ "$lowflag" != "$midflag" ]
        do
        if[ "$thisnum" \> "${arr_lownug[${midflag}]}" -o "$thisnum" == \
        "${arr_lownug[${midflag}]}" ]
        then
        lowflag=${midflag}
        midflag=$(expr `expr ${topflag} + ${lowflag}` / 2 ) #標(biāo)注4
        elif["$thisnum"\<"${arr_lownug[${midflag}]}" -o "$thisnum" == \
        "${arr_lownug[${midflag}]}" ]
        then
        topflag=${midflag}
        midflag=$(expr `expr ${topflag} + ${lowflag}` / 2 ) #標(biāo)注5
        else
        echo "Error!"  
        fi
        done
        if [ "$thisnum" \< "${arr_topnug[${lowflag}]}" -o "$thisnum" == \
        "${arr_topnug[${lowflag}]}" ]
        then
        return 1
        else
        return 0
        fi
        fi
        }#函數(shù)定義完畢

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

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

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

        程序代碼如下:
      ―――――――――――――――――――――――――――――――――――――――
        #!/bin/bash
        #Author Xsh  date:08-15-2006
        echo "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 #循環(huán)讀取號段文件下限號段存入變量tmplow,上限號段存入變量tmptop

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

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

        while [ "$lowflag" != "$midflag" ]
        do
          if ["$thisnum"\> "${arr_lownug[${midflag}]}" -o "$thisnum" == \
                  "${arr_lownug[${midflag}]}"]
                    then
                    lowflag=${midflag}
                    midflag=$(( $((${topflag} + ${lowflag})) / 2 )) #標(biāo)注4
        elif["$thisnum"\<"${arr_lownug[${midflag}]}" -o "$thisnum" == \
          "${arr_lownug[${midflag}]}"]
        then    
           topflag=${midflag}
              midflag=$(($((${topflag} + ${lowflag})) / 2 )) #標(biāo)注5
        else
           echo "Error!"  
        fi
        done
        if ["$thisnum" \< "${arr_topnug[${lowflag}]}" -o "$thisnum" == \
        "${arr_topnug[${lowflag}]}"]
        then
          return 1
        else
          return 0
        fi
        fi
        }#函數(shù)定義結(jié)束

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

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

      免費預(yù)約試聽課

      亚洲另类欧美综合久久图片区_亚洲中文字幕日产无码2020_欧美日本一区二区三区桃色视频_亚洲AⅤ天堂一区二区三区

      
      

      1. 天堂v亚洲国产ⅴ第一次 | 宅男666在线永久免费观看 | 亚洲欧美中文高清在线专区 | 日本不卡在线视频 | 日韩欧美亚洲中文字幕2021 | 日韩精品一区二区三区免费视频喜 |