I/O已經不再是效能瓶頸
在面試程式設計師時,我經常要求對方編寫一個簡單程式,計算文字檔案中各單詞出現的頻率。這是個很有趣的題目,不僅能測試各種程式設計技能,也足以引出更多極具深度的擴充套件議題。
我常會問,“這個程式中的效能瓶頸是什麼?”大多數人的回答則是,“從輸入檔案中讀取內容。”
我一直覺得這回答挺對的,但後來在網路論壇上看到了另一種思路:“我發現整個程式碼行的執行是分成多個步驟的,各個步驟都對應著額外的操作。只是這些一般會比I/O更快,所以我們不太在意。”
剛開始我不以為意,但之後抱著好奇,我認真分析了詞頻計算的效能細節……原來我們真的錯了,大家習以為常的“I/O很慢”觀念早已被顛覆!
沒錯,十幾、二十年前的磁碟I/O確實很慢。但現在已經2023年了,磁碟的按序檔案讀取已經非常快。
到底有多快?我用這種方法[1]測試了一下自己這臺做開發的膝上型電腦。其中count=4096 ,代表讀寫大小為4 GB。在這臺2022款戴爾XPS 13 Plus、三星PM9A1 NVMe驅動器再加Ubuntu 22.04的開發系統組合上,測試結果為:
I/O型別 | 速度(GB/s) |
---|---|
讀取(未快取) | 1.7 |
讀取(已快取) | 10.8 |
寫入(包括同步時間) | 1.2 |
寫入(不包括同步) | 1.6 |
當然,系統呼叫相對較慢。但當順序讀取或寫入時,每4 KB或64 KB(依緩衝區大小而定)才對應一次系統呼叫。真正慢的其實是網路I/O,特別是非本地網路。
這麼說來,誰才是那個詞頻計算程式的真正效能瓶頸?答案是,輸入處理/解析以及相關記憶體分配,具體包括:將輸入拆分成單詞,轉換成小寫,並使用雜湊表計算頻率。
我修改了自己的Python和Go單詞計算程式,這樣就能確切記錄過程中各個階段所消耗的時間:讀取輸入、處理(最慢的部分)、按頻率排序和輸出。我對一個413 MB大小的檔案執行了詞頻計算,裡頭包含欽定版《聖經》的100次重複,文字量相當誇張。
以下結果是3輪執行中的最好結果,計時單位為秒:
階段 | Python | Go(簡單) | Go(最佳化後) |
---|---|---|---|
讀取 | 0.384 | 0.499 | 0.154 |
處理 | 7.980 | 3.492 | 2.249 |
排序 | 0.005 | 0.002 | 0.002 |
輸出 | 0.010 | 0.009 | 0.010 |
總體 | 8.386 | 4.000 | 2.414 |
在這裡,排序和輸出部分可以忽略不計:畢竟輸入文字是100份《聖經》原文,所以唯一詞的出現機率很低。順帶一提,其實面試中也有人認為排序可能會是效能瓶頸,因為排序的本質是O(N log N),而輸入處理只是O(N)。但需要注意的是,這裡的兩個N是不同的:一個是檔案中單詞的總數,另一個只是唯一單詞的數量。
Python版[2]的核心可以歸納成以下幾行程式碼:
content = sys.stdin.read()
counts = collections.Counter(content.lower().split())
most_common = counts.most_common()
for word, count in most_common:
print(word, count)
Python可以輕鬆對文字內容進行逐行拆分,但速度略慢。所以這裡我將整個檔案讀入記憶體,再一次性加以處理。
Go簡單版[3]用的也是相同的方法,只是Go標準庫中沒有collections.Counter[4],所以我們需要自己動手實現頻率排序。
最佳化後的Go版本速度明顯更快,但也要複雜得多[5]。我們先把全文轉換為小寫字母,之後在適當的單詞邊界上做拆分,藉此減少記憶體分配操作。是的,減少記憶體分配是最佳化CPU繫結程式碼的一個妙招,感興趣的朋友不妨多試試。
這裡之所以沒有Python的優先後版本,是因為我發現Python程式碼很難做進一步最佳化(最多就是從8.4秒最佳化到7.5秒)。之所以速度快,是因為Python的核心操作是在C程式碼中進行的——所以Python自身的慢往往不影響大局。
可以看到,Go簡單版中的磁碟I/O只佔整體執行時間的14%;在最佳化版中,我們進一步提高了讀取和處理速度,這樣磁碟I/O甚至只佔總執行時長的7%。
所以我的結論是:如果各位處理的是大量資料,那磁碟I/O可能並不是效能瓶頸。只需要稍加測試,就會發現解析和記憶體分配才是拖累速度的“元兇”。
相關連結:
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/70024924/viewspace-2934196/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- Linux命令----分析系統I/O的瓶頸Linux
- [衝破核心瓶頸,讓I/O效能飆升]DPDK工程師手冊工程師
- 效能測試瓶頸調優
- A.O.史密斯推出全新冷熱即飲淨水機 “開水”不再是家庭生活瓶頸
- 如何正確定義效能瓶頸
- 用 pprof 找出程式碼效能瓶頸
- 利用PerfDog分析遊戲效能瓶頸遊戲
- Chrome執行時效能瓶頸分析Chrome
- LightDB資料庫效能瓶頸分析(一)資料庫
- 效能課堂-TPS 瓶頸精準定位
- 突破效能瓶頸,實現流程自動化
- 實用技巧:快速定位Zuul的效能瓶頸Zuul
- 效能測試-服務端瓶頸分析思路服務端
- 2020.10.6 效能課堂筆記-cpu 瓶頸分析筆記
- 漫談前端效能 突破 React 應用瓶頸前端React
- SQL Server 資料庫 最佳化 效能瓶頸SQLServer資料庫
- 使用 sar 和 kSar 來發現 Linux 效能瓶頸Linux
- 在Linux中,如何進行系統效能瓶頸分析?Linux
- 五個容易錯過的 PostgreSQL 查詢效能瓶頸SQL
- 2020.10.8 效能課堂筆記-記憶體瓶頸分析筆記記憶體
- 伺服器IO瓶頸對MySQL效能的影響伺服器MySql
- 效能之殤:從馮·諾依曼瓶頸談起
- 擴充套件jwt解決oauth2 效能瓶頸套件JWTOAuth
- 高併發下log4j的效能瓶頸
- 顯示卡瓶頸是什麼,如何識別顯示卡GPU瓶頸並解決以提升PC效能GPU
- 如何突破前端瓶頸???前端
- 前端瓶頸如何打破???前端
- 效能測試瓶頸之CPU問題分析與調優
- Redis效能瓶頸揭秘:如何最佳化大key問題?Redis
- 人到中年了的瓶頸
- 如何監測 Linux 的磁碟 I/O 效能Linux
- 打破儲存效能瓶頸,杉巖資料為AI提速增效AI
- NVMe儲存效能瓶頸的主要來源:檔案系統
- 效能之殤 | 分散式計算、超級計算機與神經網路共同的瓶頸分散式計算機神經網路
- 打破Kafka帶來的瓶頸?Kafka
- printStackTrace()造成的併發瓶頸
- 杉巖資料物件儲存替換IBM FileNet,突破效能瓶頸物件IBM
- 效能分析(6)- 如何迅速分析出系統 CPU 的瓶頸在哪裡