開發中Performance的那些事兒

2021-10-09 20:04:08 字數 4787 閱讀 5693

「效能」這個詞,不管是在日常生活還是寫程式的時候,都經常被提到。比方說,買新電腦的時候,我們會說「原來的電腦效能跟不上了」;寫程式的時候,我們會說,「這個程式效能需要優化一下」。那麼,你有沒有想過,我們常常掛在嘴邊的「效能」到底指的是什麼呢?我們能不能給效能下乙個明確的定義,然後來進行準確的比較呢?

在計算機組成原理乃至體系結構中,「效能」都是最重要的乙個主題。我在前面說過,學習和研究計算機組成原理,就是在理解計算機是怎麼運作的,以及為什麼要這麼運作。「為什麼」所要解決的事情,很多時候就是提公升「效能」。

什麼是效能?時間的倒數

計算機的效能,其實和我們幹體力勞動很像,好比是我們要搬東西。對於計算機的效能,我們需要有個標準來衡量。這個標準中主要有兩個指標。第乙個是響應時間(response time)或者叫執行時間(execution time)。想要提公升響應時間這個效能指標,你可以理解為讓計算機「跑得更快」。

第二個是吞吐率(throughput)或者頻寬(bandwidth),想要提公升這個指標,你可以理解為讓計算機「搬得更多」。

所以說,響應時間指的就是,我們執行乙個程式,到底需要花多少時間。花的時間越少,自然效能就越好。

而吞吐率是指我們在一定的時間範圍內,到底能處理多少事情。這裡的「事情」,在計算機裡就是處理的資料或者執行的程式指令。

和搬東西來做對比,如果我們的響應時間短,跑得快,我們可以來回多跑幾趟多搬幾趟。所以說,縮短程式的響應時間,一般來說都會提公升吞吐率。

除了縮短響應時間,我們還有別的方法嗎?當然有,比如說,我們還可以多找幾個人一起來搬,這就類似現代的伺服器都是 8 核、16 核的。人多力量大,同時處理資料,在單位時間內就可以處理更多資料,吞吐率自然也就上去了。

提公升吞吐率的辦法有很多。大部分時候,我們只要多加一些機器,多堆一些硬體就好了。但是響應時間的提公升卻沒有那麼容易,因為 cpu 的效能提公升其實在 10 年前就處於「擠牙膏」的狀態了,所以我們得慎重地來分析對待。下面我們具體來看。

我們一般把效能,定義成響應時間的倒數,也就是:

效能 = 1/ 響應時間

這樣一來,響應時間越短,效能的數值就越大。同樣乙個程式,在 intel 最新的 cpu coffee lake 上,只需要 30s 就能執行完成,而在 5 年前 cpu sandy bridge 上,需要 1min 才能完成。那麼我們自然可以算出來,coffee lake 的效能是 1/30,sandy bridge 的效能是 1/60,兩個的效能比為 2。於是,我們就可以說,coffee lake 的效能是 sandy bridge 的 2 倍。

過去幾年流行的手機跑分軟體,就是把多個預設好的程式在手機上執行,然後根據執行需要的時間,算出乙個分數來給出手機的效能評估。而在業界,各大 cpu 和伺服器廠商組織了乙個叫作 spec(standard performance evaluation corporation)的第三方機構,專門用來指定各種「跑分」的規則。

計算機的計時單位:cpu 時鐘

雖然時間是乙個很自然的用來衡量效能的指標,但是用時間來衡量時,有兩個問題。

第乙個就是時間不「準」。如果用你自己隨便寫的乙個程式,來統計程式執行的時間,每一次統計結果不會完全一樣。有可能這一次花了 45ms,下一次變成了 53ms。

為什麼會不准呢?這裡面有好幾個原因。首先,我們統計時間是用類似於「掐秒錶」一樣,記錄程式執行結束的時間減去程式開始執行的時間。這個時間也叫 wall clock time 或者 elapsed time,就是在執行程式期間,掛在牆上的鐘走掉的時間。

但是,計算機可能同時執行著好多個程式,cpu 實際上不停地在各個程式之間進行切換。在這些走掉的時間裡面,很可能 cpu 切換去執行別的程式了。而且,有些程式在執行的時候,可能要從網路、硬碟去讀取資料,要等網路和硬碟把資料讀出來,給到記憶體和 cpu。所以說,要想準確統計某個程式執行時間,進而去比較兩個程式的實際效能,我們得把這些時間給刨除掉

那這件事怎麼實現呢?linux 下有乙個叫 time 的命令,可以幫我們統計出來,同樣的 wall clock time 下,程式實際在 cpu 上到底花了多少時間。

我們簡單執行一下 time 命令。它會返回三個值,第乙個是 real time,也就是我們說的 wall clock time,也就是執行程式整個過程中流逝掉的時間;第二個是 user time,也就是 cpu 在執行你的程式,在使用者態執行指令的時間;第三個是 sys time,是 cpu 在執行你的程式,在作業系統核心裡執行指令的時間。而程式實際花費的 cpu 執行時間(cpu time),就是 user time 加上 sys time。

$ time seq 1000000

| wc -l

1000000

real 0m0.

101s

user 0m0.

031s

sys 0m0.

016s

在我給的這個例子裡,你可以看到,實際上程式用了 0.101s,但是 cpu time 只有 0.031+0.016 = 0.047s。執行程式的時間裡,只有不到一半是實際花在這個程式上的。

其次,即使我們已經拿到了 cpu 時間,我們也不一定可以直接「比較」出兩個程式的效能差異。即使在同一臺計算機上,cpu 可能滿載執行也可能降頻執行,降頻執行的時候自然花的時間會多一些。

除了 cpu 之外,時間這個效能指標還會受到主機板、記憶體這些其他相關硬體的影響。所以,我們需要對「時間」這個我們可以感知的指標進行拆解,把程式的 cpu 執行時間變成 cpu 時鐘週期數(cpu cycles)和 時鐘週期時間(clock cycle)的乘積。

程式的 cpu 執行時間 =cpu 時鐘週期數×時鐘週期時間

我們先來理解一下什麼是時鐘週期時間。你在買電腦的時候,一定關注過 cpu 的主頻。比如我手頭的這台電腦就是 intel core-i7-7700hq 2.8ghz,這裡的 2.8ghz 就是電腦的主頻(frequency/clock rate)。這個 2.8ghz,我們可以先粗淺地認為,cpu 在 1 秒時間內,可以執行的簡單指令的數量是 2.8g 條。

如果想要更準確一點描述,這個 2.8ghz 就代表,我們 cpu 的乙個「鐘錶」能夠識別出來的最小的時間間隔。就像我們掛在牆上的掛鐘,都是「滴答滴答」一秒一秒地走,所以通過牆上的掛鐘能夠識別出來的最小時間單位就是秒。

而在 cpu 內部,和我們平時戴的電子石英表類似,有乙個叫晶體振盪器(oscillator crystal)的東西,簡稱為晶振。我們把晶振當成 cpu 內部的電子錶來使用。晶振帶來的每一次「滴答」,就是時鐘週期時間。

在我這個 2.8ghz 的 cpu 上,這個時鐘週期時間,就是 1/2.8g。我們的 cpu,是按照這個「時鐘」提示的時間來進行自己的操作。主頻越高,意味著這個錶走得越快,我們的 cpu 也就「被逼」著走得越快。

如果你自己組裝過台式電腦的話,可能聽說過「超頻」這個概念,這說的其實就相當於把買回來的 cpu 內部的鐘給調快了,於是 cpu 的計算跟著這個時鐘的節奏,也就自然變快了。當然這個快不是沒有代價的,cpu 跑得越快,散熱的壓力也就越大。就和人一樣,超過生理極限,cpu 就會崩潰了。

我們現在回到上面程式 cpu 執行時間的公式。

程式的 cpu 執行時間 =cpu 時鐘週期數×時鐘週期時間

最簡單的提公升效能方案,自然縮短時鐘週期時間,也就是提公升主頻。換句話說,就是換一塊好一點的 cpu。不過,這個是我們這些軟體工程師控制不了的事情,所以我們就把目光挪到了乘法的另乙個因子——cpu 時鐘週期數上。如果能夠減少程式需要的 cpu 時鐘週期數量,一樣能夠提公升程式效能。

對於 cpu 時鐘週期數,我們可以再做乙個分解,把它變成「指令數×每條指令的平均時鐘週期數(cycles per instruction,簡稱 cpi)」。不同的指令需要的 cycles 是不同的,加法和乘法都對應著一條 cpu 指令,但是乘法需要的 cycles 就比加法要多,自然也就慢。在這樣拆分了之後,我們的程式的 cpu 執行時間就可以變成這樣三個部分的乘積。

程式的 cpu 執行時間 = 指令數×cpi×clock cycle time

因此,如果我們想要解決效能問題,其實就是要優化這三者。

時鐘週期時間,就是計算機主頻,這個取決於計算機硬體。我們所熟知的摩爾定律就一直在不停地提高我們計算機的主頻。比如說,我最早使用的 80386 主頻只有 33mhz,現在手頭的膝上型電腦就有 2.8ghz,在主頻層面,就提公升了將近 100 倍。

每條指令的平均時鐘週期數 cpi,就是一條指令到底需要多少 cpu cycle。在後面講解 cpu 結構的時候,我們會看到,現代的 cpu 通過流水線技術(pipeline),讓一條指令需要的 cpu cycle 盡可能地少。因此,對於 cpi 的優化,也是計算機組成和體系結構中的重要一環。

指令數,代表執行我們的程式到底需要多少條指令、用哪些指令。這個很多時候就把挑戰交給了編譯器。同樣的**,編譯成計算機指令時候,就有各種不同的表示方式。

我們可以把自己想象成乙個 cpu,坐在那裡寫程式。計算機主頻就好像是你的打字速度,打字越快,你自然可以多寫一點程式。cpi 相當於你在寫程式的時候,熟悉各種快捷鍵,越是打同樣的內容,需要敲擊鍵盤的次數就越少。指令數相當於你的程式設計得夠合理,同樣的程式要寫的**行數就少。如果三者皆能實現,你自然可以很快地寫出乙個優秀的程式,你的「效能」從外面來看就是好的。

android開發中的

線性布局 linear layout 相對布局 relative layout 布局 table layout 網格檢視 grid view 標籤布局 tab layout 列表檢視 list view 絕對布局 absolutelayout 1.實現tab的效果必須使用tabhost控制項作為ta...

開發中的思考

一直在工作之餘,閱讀kent beck的實現模式,在開發之餘,寫一些心得 1.在開發前寫出乙個簡單明瞭的todolist,學會帶著問題去思考。2.讓自己的思維很清晰地線性化!3.對於邏輯和資料來講,有時候業務資料會發生變化,那麼只需要修改資料。如修改乙個檢視的結構或者表的內容等,而不需要對邏輯結構做...

敏捷開發 敏捷開發中的質量

有小夥伴就問,我們都敏捷了,我們是在效率和質量中找平衡,說敏捷開發中的質量是不容易控制的,要回答這個問題,我設計了乙個faq,內容如下 敏捷開發是什麼?敏捷開發是以需求為中心,以交付價值為目的,持續增量交付的一種軟體開發方法,至於什麼是敏捷,就去問問度娘吧。對於敏捷團隊來說,是乙個自組織的,有集體目...