關於系統效能的10大錯誤
Martin Thompson是LMAX的聯合創始人,在QCon聖保羅2016上做過關於效能的keynote演講。他最初計劃的演講題目為“關於效能的神話與傳說”,不過Thompson後來將演講命名為“十大效能錯誤”,因為“我們都會犯錯誤,而且很容易就會出現錯誤”。
下面列出了他在生產環境下所見到的十大效能錯誤,並且還包含了如何避免的建議。
10.不進行升級
很多人抱怨他們的系統不夠快,並通過編寫更好的演算法和資料結構來尋求幫助,Thompson認為實際上“他們所需的僅僅就是進行升級”。升級作業系統、JVM、CLR等等。不進行升級的常見藉口就是“在新版本中可能會有bug。”
為了避免這種狀況,可以進行定期的持續整合和測試,這應該是開發流程的基礎組成部分。Thompson以一個實時系統進行了例證,開發人員針對新版本的資料庫進行了測試,在所有的測試通過之後,他們就將其釋出到了生產環境之中。
9.重複性的工作
Thompson講述了某個系統的故事,這個系統是用來提供Web頁面的,它非常緩慢,開發人員最初認為是資料庫的問題並試圖在這方面進行調優。但是當他在系統上執行profiler時,發現在一個迴圈中,ORM被呼叫了7,000次,這才是頁面載入緩慢的罪魁禍首。當這個迴圈的問題修復之後,系統的響應變得完全正常。這裡學到的經驗就是“對系統進行度量。如果系統是一個黑盒的話,你就無法說明時間都耗費在了哪裡。”
8. 載入效能依賴於資料
Thompson展現了一個基準測試結果,它會執行一項操作,該操作會對記憶體(RAM)中1GB陣列的所有long型進行求和。這裡所耗費的時間取決於記憶體是如何訪問的,如下面的表格所示:
這個基準測試的結果顯示並非所有的記憶體操作都是等價的,我們需要關注它是如何進行處理的。Thompson認為非常重要的一點在於瞭解各種資料結構的效能,他指出對於2GB以上的場景,Java的HashMap要比.NET的Dictionary慢十倍以上。他還補充說,也有一些場景.NET要比Java慢得多。
7. 分配的記憶體太多
儘管在很多場景中,記憶體分配幾乎是沒有什麼成本的,但是它們的回收卻並非如此,因為在面對大量的資料集時,垃圾收集器需要更多的時間。當分配大量的資料時,快取會被填滿,較舊的資料會被捨棄,使得在資料操作上的效率變為90ns/op而不是7ns/op,這裡變慢了不止一個數量級。
6. 採用並行
儘管對於特定的演算法來說,採用並行很有吸引力,但是它也有一些侷限性和相關的開銷。Thompson引用了“可擴充套件性!但是其COST如何?”這篇論文,論文的作者通過引入COST(勝過單執行緒的配置,Configuration that Outperforms a Single Thread)對比了並行系統以及單執行緒的系統,COST的定義如下:
在特定的平臺中,特定問題的COST指的是優於單執行緒方案所需的硬體配置。COST將系統的擴充套件性與系統所引入的開銷進行了權衡,並指明瞭系統實際所能取得的效能,它們可能並沒有帶來實際的收益,卻增加了並行所引入了開銷。
作者分析了各種資料並行系統的測量結果,並得出如下的結論:“很多的系統要麼具有非常高的COST,通常會需要上百個核心,要麼針對他們所報告的配置,其效能要比單執行緒方案更差。”
在這個話題中,Thompson指出,並行任務會有相關的通訊和同步開銷,並且有些活動本質上要求是序列的,不能實現並行。按照Amdahl定律,如果系統中有5%的活動需要序列,那麼不管使用了多少個處理器,系統的速度提升最多隻能達到20倍。
Thompson還提到了Neil J. Gunther在1993年所提出的通用可擴充套件性定律(Universal Scalability Law,PDF),該定律指出在並行非共享系統(shared-nothing)中甚至會存在更多的侷限性,當所使用的處理器數量達到一定程度後,速度會出現下降,這取決於併發、競爭以及同步的水平。(更多的細節可以參考如何量化可擴充套件性這個頁面。)按照上述兩個規律所總結的速度與處理器數量之間的關係如下圖所示:
Thompson指出通過USL能夠看到效能的下降,這要歸因於並行系統中元件之間進行通訊所消耗的成本:“在系統中,所投入資源越多,通訊路徑也會隨之增多,這會使演算法的效率降低。”
Thompson補充說,在構建並行系統時,主要的建議是避免共享可變(mutable)的狀態,因為“它非常難以進行判斷……最終你會遇到很多的bug”。推薦的方式是要麼採用非共享架構,要麼針對特定的一塊資料,只使用一個寫入器。
對這個效能問題,他的最終建議:如果你想提升演算法的速度的話,在嘗試並行方案之前,先設法提升單執行緒版本的效能,因為並行方案實在是太難了。
5. 不理解TCP
針對這個話題,Thompson認為很多在考慮微服務架構的人對TCP並沒有充分的理解。在特定的場景中,有可能會遇到延遲的ACK,它會限制鏈路上所傳送的資料包,每秒鐘只會有2-5個資料包。這是因為TCP兩個演算法所引起的死鎖:Nagle以及TCP Delayed Acknowledgement。在200-500ms的超時之後,會打破這個死鎖,但是微服務之間的通訊卻會分別受到影響。推薦的方案是使用TCP_NODELAY,它會禁用Nagle的演算法,多個更小的包可以依次傳送。按照Thompson的說法,其中的差別在5到500 req/sec。
4. 同步通訊
客戶端和伺服器之間的同步通訊會帶來時間的損耗,對於需要快速通訊的系統來說,這會成為一個問題。Thompson說,它的解決方案並不是購買更加昂貴和快速的硬體,而是使用非同步通訊。在這種場景下,客戶端可以傳送多個請求到伺服器端,而不必等待它們之間的響應。採用這種方式需要改變客戶端傳送請求的方式,但這是值得的。
3. 文字編碼
開發人員很多時候會選擇使用文字編碼格式實現鏈路上的資料傳輸,比如JSON、XML或Base64,因為“這對人類是可讀的”。但是Thompson指出在兩個系統之間進行對話的時候,是沒有人讀這些資料的。藉助這種方式,使用簡單的文字編輯器就能很容易地進行除錯,但是在將二進位制資料與文字之間進行互相轉換的時候,這會帶來很高的CPU損耗。該問題的解決方案是使用能夠理解二進位制的更好的工具,Thompson提到了Wireshark。
2. API設計
按照Thompson的說法,有一些與效能相關的最負面影響是由API引起的。它使用如下的程式碼來闡述較差的程式碼簽名:
public void startElement( String uri, String localName, String qName, Attributes atts) throws SAXException
描述:
在處理XML的時候,通常我們並不會使用這些值[三個String以及屬性的集合]。我們分配了很多的內容,但是卻將其浪費並拋棄掉了。這會損耗電池的壽命,白白地浪費資源。我們需要使其更加簡單一些。
他建議採用如下的簽名,實現更加簡單的方法:
public void characters( char[] ch, int start, int length) throws SAXException
有些人可能會抱怨後面的這個方法要比前一個更難用,Thompson建議採用組合的方式,將其中一個用另一個封裝起來,這樣的話,能夠給使用者多一個選擇。如果效能不是什麼問題的話,可以採用第一種(使用String),否則的話,第二個方案會更好一些。
Thompson提到的第二個樣例是字串拆分:
public String[] split(String regex)
這個方法簽名相關的效能問題包括:
- 每次方法呼叫的時候,正規表示式都需要進行編譯;
- 需要例項化一個動態的結構,用來儲存字串中所包含的初始數量未知的token;
- 返回的結構是一個固定大小的陣列,這就必須要將token收集到一個臨時的結構中,然後再拷貝到陣列裡面;
- 如果呼叫者想要對這些token進行一些操作的話,比如排序,需要將它們拷貝到另外一個結構之中。
更好的方案是使用Iterable,它能夠避免在記憶體中建立中間狀態的token副本:
public Iterable split(String regex)
另外一種方案是允許呼叫者提供儲存token的集合。如果呼叫者想要對token列表去重的話,應該傳遞一個Set進來,如果想得到有序列表的話,就需要傳遞一個TreeMap進來:
public void split( String regex, Collection dst)
1.日誌
Thompson所列的排名第一的效能問題是寫日誌所耗費的時間。他通過一個圖表展現了當執行緒數增加的時候,日誌操作所耗費的平均時間:
這個圖顯示了一個100%的順序操作,不管使用多少執行緒來記錄日誌,所需的時間均呈線性增長。Thompson說大多數已有的日誌系統都可以得出這樣一幅圖表,“Logger是系統中最大的瓶頸之一”。這個問題的解決方案是使用非同步的Logger。
另外,Logger所記錄的資料應該是結構化的資料,便於後續的工具進行讀取和處理,而不應該是一堆String。如果是記錄重複的錯誤,他建議在錯誤第一次出現的時候進行記錄,後續出現時只需對一個計數器進行遞增,告知對應的錯誤出現了多少次即可。對於實時系統的除錯,Thompson建議使用程式碼編織(code weaver)的技術,如Byte Buddy,因為它能夠避免編寫和執行不必要的日誌程式碼。
這個分享的講義可以線上獲取(PDF)。
相關文章
- 關於vuex的錯誤Vue
- oracle關於ORA-12988錯誤Oracle
- 關於洗牌演算法的錯誤認識演算法
- 關於Mapreduce Text型別賦值的錯誤型別賦值
- 作業系統錯誤點作業系統
- 關於 IIS 上執行 ASP.NET Core 站點的“HTTP 錯誤 500.19”錯誤ASP.NETHTTP
- Maven關於配置setting.xml出現的錯誤MavenXML
- 搭建前端錯誤監控系統前端
- vscode中關於eslint的各種報黃線錯誤VSCodeEsLint
- [譯] 關於 `ExpressionChangedAfterItHasBeenCheckedError` 錯誤你所需要知道的事情ExpressError
- 關於 ABAP 的執行時錯誤 ITAB_ILLEGAL_ORDER
- win10系統提示系統錯誤由於找不到DUILIB.DLL如何解決Win10UI
- 我的第一個系統管理員錯誤
- 關於 curl 工作中一個小錯誤
- Linux/Unix系統中主機HBA無法發現LUN,路徑顯示錯誤,LUN相關錯誤Linux
- mysql-發生系統錯誤1067MySql
- 關於安裝ros和執行tuetlebot3的錯誤ROS
- 關於開源軟體的七大錯誤認知
- 關於 SAP Cloud Connector 500 failed to sign the Certificate 的錯誤訊息CloudAI
- [記錄]關於安裝VMware workstation 時我遇到的錯誤
- mysql 發生系統錯誤1067的解決方法MySql
- 使用 SAP WebIDE 將 SAP UI5 應用部署到 ABAP 系統時遇到的關於傳輸請求的錯誤WebIDEUI
- 前端面試10:錯誤監控/產品效能體系前端面試
- 關於linux類系統的操作Linux
- 解決ubuntu系統“XXX is not in the sudoers file”錯誤Ubuntu
- 關於程式碼的那些低階錯誤,都是血淚的教訓
- win10系統怎麼檢視系統錯誤日誌_win10如何檢視錯誤日誌Win10
- 提供統一的錯誤APIAPI
- 關於pip安裝時提示"pkg_resources.DistributionNotFound"錯誤
- 關於ESLint: Delete `␍`(prettier/prettier) 錯誤解決方案(3種)EsLintdelete
- 關於laravel Symfony\Component\HttpKerenl\Exception\Method...錯誤資訊LaravelHTTPException
- 關於錯誤訊息 RangeError - Maximum call stack size exceeded at XXXError
- win10系統錯誤oxc0000005解決方案_win10系統錯誤oxc0000005修復方法Win10
- Windows下make clean指令錯誤[錯誤碼2](系統找不到指定檔案)的解決方案Windows
- 作業系統錯誤5:拒絕訪問作業系統
- Git相關 | Git 常見的錯誤Git
- 分散式系統–>(關於系統應用的基本概念)分散式
- 基於 React Redux 的錯誤處理ReactRedux
- win10系統lsp錯誤怎樣修復_win10修復lsp錯誤的步驟Win10