日日操夜夜添-日日操影院-日日草夜夜操-日日干干-精品一区二区三区波多野结衣-精品一区二区三区高清免费不卡

公告:魔扣目錄網為廣大站長提供免費收錄網站服務,提交前請做好本站友鏈:【 網站目錄:http://www.ylptlb.cn 】, 免友鏈快審服務(50元/站),

點擊這里在線咨詢客服
新站提交
  • 網站:51998
  • 待審:31
  • 小程序:12
  • 文章:1030137
  • 會員:747

聽診器是一種簡單工具,卻給醫生的工作帶來了革命:它讓內科醫生能有效地監控病人的身體。性能監視工具(profiler)對程序起著同樣的作用。

你現在用什么工具來研究程序?復雜的分析系統很多,既有交互式調試器,又有程序動畫系統。正如CT掃描儀永遠代替不了聽診器一樣,復雜的軟件也永遠代替不了程序員用來監控程序的最簡單工具——性能監視工具,我們用它了解程序各部分的執行頻率。

本文先用兩種性能監視工具來加速一個小程序(記住真正的目的是說明性能監視工具)。后續各節簡要介紹性能監視工具的各種用途、非過程語言的性能監視工具,以及開發性能監視工具的技術。

1 計算素數

程序P1是個ANSI標準C程序,依次打印所有小于1000的素數。

程序P1

    int prime(int n)
    {  int i;
999      for (i = 2; i < n; i++)
78022      if(n%i == 0)
831         return 0;
168     return 1;
    }
    main()
    {  int i, n;
1      n = 1000;
1      for (i = 2; i <= n; i++)
999       if (prime(i))
168         printf("%dn", i);
    }

如果整型參數n是素數,上述prime函數返回1(真),否則返回0。這個函數檢驗2到n-1之間的所有整數,看其是否整除n。上述main過程用prime子程序來依次檢查整數2~1000,發現素數就打印出來。

我像寫任何一個C程序那樣寫好程序P1,然后在性能監視選項下進行編譯。在程序運行之后,只要一個簡單的命令就生成了前面所示的列表。(我稍微改變了一些輸出的格式。)每行左側的數由性能監視工具生成,用于說明相應的行執行了多少次。例如,main函數調用了1次,其中測試了999個整數,找出了168個素數。函數prime被調用了999次,其中168次返回1,另外831次返回0(快速驗證:168+ 831=999)。prime函數共測試了78022個可能的因子,或者說為了確定素數性,對每個整數檢查了大約78個因子。

程序P1是正確的,但是很慢。在VAX-11/750上,計算出小于1000的所有素數約需幾秒鐘,但計算出小于10 000的所有素數卻需要3分鐘。對這些計算的性能監視表明,大多數時間花在了測試因子上。因而下一個程序只對n考慮不超過sqrt n 的可能的整數因子。整型函數root先把整型參數轉換成浮點型,然后調用庫函數sqrt,最后再把浮點型結果轉換回整型。程序P2包含兩個舊函數和這個新函數root。

程序P2

     int root(int n)
5456   { return (int) sqrt((float) n);  }

     int prime(int n)
     {  int i;
999     for (i = 2; i < = root(n); i++)
5288       if (n % i == 0)
831          return 0;
168     return 1;
     }

     main()
     {  int i, n;
1       n = 1000;
1       for (i = 2; i < = n; i++)
999        if (prime(i))
168          printf("%dn", i);
     }

修改顯然是有效的:程序P2的行計數顯示,只測試了5288個因子(程序P1的1/14),總共調用了5456次root(測試了5288次整除性,168次由于i超出了root(n)而終止循環)。不過,雖然計數大大減少了,但是程序P2運行了5.8秒,而程序P1只運行了2.4秒(本節末尾的表中含有運行時間的更多細節)。這說明什么呢?

迄今為止,我們只看到了行計數(line-count)性能監視。過程時間(procedure-time)性能監視給出了較少的控制流細節,但更多地揭示了CPU時間:

  %time  cumsecs     #call    ms/call       name 
   82.7    4.77                    _sqrt
   4.5    5.03     999      0.26       _prime
   4.3    5.28     5456      0.05       _root
   2.6    5.43                    _frexp
   1.4    5.51                    _ _doprnt
   1.2    5.57                     _write
   0.9    5.63                    mcount
   0.6    5.66                    _creat
   0.6    5.69                    _printf
   0.4    5.72       1     25.00       _main
   0.3    5.73                    _close
   0.3    5.75                    _exit
   0.3    5.77                    _isatty

過程按照運行時間遞減的順序列出。時間上既顯示出總秒數,也顯示出占總時間的百分比。編譯后記錄下源程序中main、prime和root這3個過程的調用次數。再次看到這幾個計數是令人鼓舞的。其他過程沒有性能監視的庫函數,完成各種輸入/輸出和清理維護工作。第4列說明了帶語句計數的所有函數每次調用的平均毫秒數。

過程時間性能監視說明,sqrt占用CPU時間的最多:該函數共被調用5456次,for循環的每次測試都要調用一次sqrt。程序P3通過把sqrt調用移到循環之外,使得在prime的每次調用中只調用一次費時的sqrt過程。

程序P3

     int prime(int n)
     {  int i, bound;
999      bound = root(n);
999      for (i = 2; i < = bound; i++)
5288        if (n % i == 0)
831           return 0;
168        return 1;
    }

當n=1000時,程序P3的運行速度大約是程序P2的4倍,而當n= 100 000時則超過10倍。以n= 100 000的情形為例,過程時間性能監視顯示,sqrt占用了程序P2的88%的運行時間,但是只占用了程序P3的48%的運行時間。這好多了,但仍然是循環的累贅。

程序P4合并了另外兩個加速措施。首先,程序P4通過對被2、3和5整除的特殊檢驗,避免了近3/4的開方運算。語句計數表明,被2整除的性質大約把一半的輸入歸入合數,被3整除把剩余輸入的1/3歸入合數,被5整除再把剩下的這些數的1/5歸入合數。其次,只考慮奇數作為可能的因子,在剩余的數中避免了大約一半的整除檢驗。它比程序P3大約快兩倍,但是也比P3的錯誤更多。下面是(帶錯的)程序P4,你能通過檢查語句計數看出問題嗎?

程序P4

     int root(int n)
265    {  return (int) sqrt((float) n); }

     int prime(int n)
     {  int i, bound;
999      if (n % 2 == 0)
500        return 0;
499      if (n % 3 == 0)
167        return 0;
332      if (n % 5 == 0)
67        return 0;
265      bound = root(n);
265      for (i = 7; i <= bound; i = i+2)
1530        if (n % i == 0)
100           return 0;
165      return 1;
    }
    main()
    {   int i, n;
1       n = 1000;
1       for (i = 2; i <= n; i++)
999        if (prime(i))
165          printf("%dn", i);
    }

先前的程序找到168個素數,而程序P4只找到165個。丟失的3個素數在哪里?對了,我把3個數作為特殊情形,每個數都引入了一處錯誤:prime報告2不是素數,因為它被2整除。對于3和5,存在類似的錯誤。正確的檢驗是

if (n % 2 == 0)
  return (n == 2);

依此類推。如果n被2整除,如果n是2就返回1,否則返回0。對于n=1000、10 000和100 000,程序P4的過程時間性能監視結果總結在下表中。

程序的“聽診器”——性能監視工具

 

程序P5比程序P4快,并且有個好處:正確。它把費時的開方運算換成了乘法,如以下程序片段所示。

程序P5的片段

265       for (i = 7; i*i <= n; i = i+2)
1530         if (n % i == 0)
100            return 0;
165       return 1;

它還加入了對被2、3、5整除的正確檢驗。程序P5總的加速大約有20%。

最后的程序只對已被判定為素數的整數檢驗整除性;程序P6在1.4節,用Awk語言寫成。C實現的過程時間性能監視結果表明,在n=1 000時,49%的運行時間花在prime和main上(其余是輸入/輸出);而當n=100 000時,88%的運行時間花在這兩個過程上。

下面這個表總結了我們已經看到的這幾個程序。表中還包含另外兩個程序作為測試基準。程序Q1用習題答案2中的埃氏篩法程序計算素數。程序Q2測量輸入/輸出開銷。對于n=1 000,它打印整數1, 2,…, 168;對于一般的n,它打印整數1, 2,…, P(n),其中P(n)是比n小的素數的個數。

程序的“聽診器”——性能監視工具

 

以上集中講述了性能監視的一種用途:當你調優單個子過程或函數的性能時,性能監視工具能告訴你運行時間都花在了哪里。

2 使用性能監視工具

對于小程序來說,性能監視很容易實現,因此性能監視工具是可有可無的;但是對于開發大軟件來說,性能監視工具則是不可或缺的。Brian Kernighan曾經使用行計數性能監視工具,研究了一個用于解釋Awk語言程序的4000行的C程序。那時這個Awk解釋程序已廣泛使用了多年。掃描該程序75頁長的程序清單就會發現,大多數計數都是成百上千的,有些甚至上萬。一段晦澀的初始化代碼,計數接近百萬。Kernighan對一個6行的循環做了幾處修改,程序速度就提高了一倍。他自己可能永遠也猜不出程序的問題源頭所在,但是性能監視工具引導他找到了。

Kernighan的這一經歷是相當典型的。“一個程序中不到4%的語句通常占用了一半以上的運行時間。”對許多語言和系統的大量研究表明,對于不處理I/O密集型的大多數程序,大部分的運行時間花在了很小一部分代碼上。這種模式是下述經驗的基礎:

Knuth在論文中描述了用行計數性能監視工具進行自我分析的結果。性能監視結果表明,一半的運行時間花在了兩個循環上。結果花了不到一小時修改了幾行代碼,就讓這個性能監視工具的速度提高了一倍。

性能監視結果說明,一個1000行的程序把80%的時間花在一個5行的子程序上。把這個子程序改寫成十幾行,就讓程序的速度提高了一倍。

1984年貝爾實驗室的Tom Szymanski打算給一個大系統提速,結果卻使該系統慢了10%。他刪除了修改的部分,然后多打開了一些性能監視選項以查明失敗原因。他發現占用的存儲空間增加到了原來的20倍,行計數顯示存儲空間的分配次數遠多于釋放次數。接下來用一條指令就糾正了錯誤,正確的實現讓系統加速了一倍。

性能監視表明,操作系統一半的時間花在一個只有少數幾條指令的循環上。改寫微代碼中的這個循環帶來一個量級的提速,但是系統的吞吐量不變:性能組已經優化了系統的空閑循環!

這些經歷引出了上一節粗略提到過的一個問題:應當在什么輸入上監視程序的性能?查找素數的程序只有一個輸入n,該輸入強烈影響到時間性能監視:對于小的n,輸入/輸出占大頭;對于大的n,計算占大頭。有的程序的性能監視結果對輸入數據非常不敏感。我猜想大多數計算薪水的程序都有相當一致的性能監視結果,至少從2月到11月如此。但有的程序的性能監視結果會隨輸入不同有巨大變化。難道你從沒有察覺到,你的系統被調整得在制造商的基準數據上運行起來風馳電掣,而處理起你的重要任務時卻慢如蝸牛?仔細挑選你的輸入數據吧。

性能監視工具對于性能之外的任務也有用。在找素數的練習中,它指出了程序P4的一個錯誤。行計數在估計測試覆蓋面時極有價值,比如,如果出現零,則說明有代碼未測試。DEC公司的Dick Sites這樣描述性能監視的其他用途:“(1) 在兩層微存儲實現中,決定哪些微代碼放到芯片上;(2) 貝爾北方研究院(Bell Northern Research)的一位朋友某個周末在帶有多重異步任務的實時電話交換軟件系統上實現了語句計數。通過查看異常計數,他發現了現場安裝的代碼中存在6處錯誤,所有錯誤都涉及不同任務之間的交互。其中一處錯誤用常規調試技術無法成功追蹤到,其余錯誤還沒有被當作問題(也就是說,這些錯誤癥狀可能已經發生,但是沒有人能夠將其歸結為具體的軟件錯誤)。”

3 專用的性能監視工具

到目前為止我們所看到的性能監視工具的原理,適用于從匯編和Fortran直到Ada這樣的程序設計語言,但是很多程序員現在使用更強大的語言。如何監視Lisp或APL程序的計算性能?又如何監視網絡或數據庫語言程序的計算性能?

我們打算用UNIX的管道(pipeline)作為更有趣的計算模型的例子。管道是一系列的過濾程序(filter):當數據流經每個過濾程序時,對數據施加變換。下面這個經典的管道按照頻率遞減順序打印某文件中使用最多的25個單詞。

cat $* ¦
tr -cs A-Za-z '12' ¦
tr A-Z a-z ¦
sort ¦
uniq  -c ¦
sort -r –n ¦
sed 25q

當用這個管道在一本大約6萬字的書中尋找25個最常見的單詞時,我們監視這個管道的性能。輸出的前6行是:

3463 the
1855 a
1556 of
1374 to
1166 in
1104 and
  ...

下面是對VAX-11/750上計算的“管道性能監視”:

lines   words    chars       times
10717  59701    342233   14.4u 2.3s  18r  tr -cs A-Za-z 12
57652  57651    304894   11.9u 2.2s  15r  tr A-Z a-z
57652  57651    304894   104.9u 7.5s 123r  sort  
57652  57651    304894   24.5u 1.6s 27r   uniq –c
 4731   9461    61830    27.0u 1.6s 31r   sort -rn
 4731   9461    61830    0.0u 0.2s 0r    sed 25q
  25     50     209

左邊幾列說明每個階段的數據:行數、單詞數、字符數。右邊部分描述了數據階段之間的過濾程序:用秒表示的用戶時間、系統時間以及真實時間,后面是命令本身。

這個性能監視結果給出了程序員感興趣的許多信息。這個管道是快速的,處理150頁的書只需3.5分鐘。第一次排序花了這個管道57%的運行時間,這種經過仔細調優的實用程序很難再提速了。第二次排序只花了這個管道14%的時間,但是還有調優的余地。這個性能監視結果還發現了管道中隱藏的一處小錯誤。UNIX高手們會樂于找出引入空行的地方。

這個性能監視結果也透露了文件中單詞的信息:共有57 651個單詞,但只有4731個不同的單詞。在第一個翻譯程序之后,每個單詞有4.3個字母。輸出表明,最常見的單詞是“the”,占了文件的6%。6個最常見的單詞占了文件的18%。對英語中最常見的100個單詞做專門處理也許還能提高速度。試試看從這些計數中找出其他有趣的表面規律。

跟許多UNIX用戶一樣,我過去也用手工監視管道的性能,利用單詞計數(wc)命令來統計文件,用time命令來統計進程。“管道性能監視工具”讓這個任務自動化了。用管道和一些輸入文件的名稱作為輸入,產生性能監視結果作為輸出。2個小時和50行代碼就足以建立這個性能監視工具。下一節詳細闡述這個話題。

4 開發性能監視工具

開發一個真正的性能監視工具是件困難的事情。Peter Weinberger開發了C行計數性能監視工具,我們前面看到的輸出就是這個工具產生的。他在幾個月時間內斷斷續續干了好幾周才完成這個項目。本節描述如何更容易地開發一個簡化版本。

Dick Sites聲稱他的朋友“在某個周末實現了語句計數”。我覺得這簡直難以置信,于是我決定要試著為附錄A描述的Awk語言(這種語言還沒有性能監視工具)開發一個性能監視工具。幾小時后,當我運行程序P6的Awk版本時,我的性能監視工具生成了如下輸出。

程序P6及性能監視工具生成的輸出

BEGIN { <<<1>>>
   n = 1000
   x[0] = 2; xc = 1
   print 2
   for (i = 3; i <= n; i++) { <<<998>>>
     if (prime(i))  { <<<167>>>
        print i
     }
   }
   exit
}
function prime(n,  i) { <<<998>>>
   for (i=0; x[i]*x[i]<=n; i++) { <<<2801>>>
      if (n % x[i] == 0) { <<<831>>>
        return 0
      }
   }
   { <<<167>>> }
   x[xc++] = n
   return 1
}

在左花括號后尖括號內的數顯示該語句塊被執行了多少次。幸運的是,這些計數與C行計數器產生的計數一樣。

我的性能監視工具包含兩個5行的Awk程序。第一個程序讀Awk源程序并且寫一個新程序,其中在每個語句塊開始的地方給不同的計數器加1;而在執行結束時,一個新的END動作(見附錄A)把所有計數寫入一個文件。當這樣得出的程序運行時,就生成一個計數文件。第二個程序讀出這些計數,把這些計數合并到源文本中。帶性能監視的程序大約比原來的程序慢25%,而且并不是所有的Awk程序都能正確處理——為了監視幾個程序的性能,我不得不做出整行(one-line)的修改。但對于所有這些缺點來說,搭起一個能運行的性能監視工具,花幾小時并不算什么大投入。

人們實現過一些快速性能監視工具,但鮮見報道。下面舉幾個例子。

在1983年8月的 BYTE 雜志上,Leas和Wintz描述了一個性能監視工具,用一個20行的6 800匯編語言程序來實現。

貝爾實驗室的Howard Trickey在一小時內用Lisp實現了函數計數,辦法是修改defun,在進入每個函數時給計數器加1。

1978年,Rob Pike用20行Fortran程序實現了一個時間性能監視工具。在CALL PROFIL(10)之后,后續的CPU時間被計入計數器10。

在這些系統和許多其他系統上,在一晚上寫出一個性能監視工具是可能的。在你第一次使用所得到的性能監視工具時,這個工具輕易就能節省超過一個晚上的工作量。

5 原理

本文只浮光掠影地介紹了性能監視。我介紹了最基礎的內容,忽略了搜集數據的其他方式(比如硬件監視器)和其他顯示方式(比如動畫系統)。本文所要傳達的信息同樣是基本的。

  • 使用性能監視工具。讓本月成為性能監視工具月。請在隨后幾周內至少監視一個程序片段的性能,并且鼓勵你的伙伴們也這樣做。記住,當一個程序員屈尊來幫助一個小程序時,并不總是高瞻遠矚的。
  • 開發性能監視工具。如果你還沒有方便的性能監視工具,就自造一個吧。大多數系統都提供基本的性能監視操作。20世紀60年代不得不觀察控制臺燈光來獲得信息的程序員,現在可從個人工作站的圖形窗口獲得同樣的信息。一個小程序通常足以把系統的命令特性包裝成方便的工具。

本文節選自《編程珠璣(續)(修訂版)》

分享到:
標簽:監視 性能
用戶無頭像

網友整理

注冊時間:

網站:5 個   小程序:0 個  文章:12 篇

  • 51998

    網站

  • 12

    小程序

  • 1030137

    文章

  • 747

    會員

趕快注冊賬號,推廣您的網站吧!
最新入駐小程序

數獨大挑戰2018-06-03

數獨一種數學游戲,玩家需要根據9

答題星2018-06-03

您可以通過答題星輕松地創建試卷

全階人生考試2018-06-03

各種考試題,題庫,初中,高中,大學四六

運動步數有氧達人2018-06-03

記錄運動步數,積累氧氣值。還可偷

每日養生app2018-06-03

每日養生,天天健康

體育訓練成績評定2018-06-03

通用課目體育訓練成績評定