发掘 CPU 与超级工厂的共性,程序员的心思你别猜
來源 | 碼農的荒島求生
責編 | 寇雪芹
頭圖 | 下載于ICphoto
18世紀流水線的誕生帶來了制造技術的變革,人類當今擁有琳瑯滿目物美價廉的商品和流水線技術的發明密不可分,因此當你喝著可樂、吹著空調、坐在特斯拉里拿著智能手機刷這篇文章時需要感謝流水線技術。
一段有趣的代碼
有這樣一段代碼:
for?(int?k?=?0;?k?<?10000;?k++){for?(int?i?=?0;?i?<?arr.size();?i++)?{if?(arr[i]?>?256)sum?+=?arr[i];} }這段代碼非常簡單,給定一個數組,計算所有大于256 的元素之和,重復計算 10000 遍。這段代碼本身平淡無奇,但有趣的是:如果這個數組是有序的,那么這段代碼的運行速度會比處理無序數組快將近 10 倍(不同的機器、CPU架構可能會稍有差異)。可這是為什么呢?這和制造業使用的流水線又有什么關系呢?且聽我慢慢道來。
流水線技術的誕生
1769年,英國人喬賽亞·韋奇伍德開辦了一家陶瓷工廠,這家工廠生產的陶瓷乏善可陳,但其內部的管理方式極具創新性,傳統的方法都是由制陶工專人來完成,但韋奇伍德研究后將整個制陶工藝流程分成了幾十道工序,每一道工序都交給專人完成,這樣傳統的制陶人不復存在,這便是工業流水線最早的雛形。
雖然流水線技術可以說是英國人發明的,但發揚光大的卻是美國人,這便是福特與T型車。
20世紀初,福特將流水線技術應用到汽車的批量生產,效率得到千倍提高,使得汽車這種奢侈品開始能夠為大眾消費,深刻影響了現代社會的方方面面,注意上圖中一輛車的價格。
100 年后又一個美國人攜帶他的時尚電動車再一次席卷全球,這就是特斯拉。
接下來我們仔細看一下流水線技術。
特斯拉與流水線
假設組裝一輛特斯拉需要經過:組裝車架、安裝引擎、安裝電池、檢驗四道工序,同時假設每個步驟需要 20 分鐘,因此如果所有工序都由一個組裝站點來完成,那么組裝一輛特斯拉需要80分鐘。
但如果每個步驟都交給一個特定站點來組裝的話就不一樣了,此時生產一輛車的時間依然是80分鐘,但這只是第一輛車所需要的時間,此后工廠可以每20分鐘就交付一輛特斯拉。
注意,流水線并沒有減少組裝一輛車的時間,只是增加了工廠的吞吐能力。流水線技術的使用極大增加了工廠交付車輛的效率。
CPU 與超級工廠
其實 CPU 本身也是一座超級工廠。只不過CPU這座工廠生產的不是特斯拉,而是機器指令。工廠內部有流水線極大提高了生產效率,CPU 沒有理由不擁有。
你可以想象一下,不管你現在看這篇文章用的是PC 還是智能手機,其內部的 CPU 都有一條復雜度不亞于特斯拉超級工廠的流水生產線。
如果我們把CPU處理的一條機器指令當做一輛特斯拉的話,那么對于現代CPU這座超級工廠來說,一秒鐘的時間內可以交付數十億量特斯拉,效率完爆任何當今制造界的工業流水線,CPU 才是一座名副其實的超級工廠。
如果特斯拉超級工廠也如 CPU 一般高效的話,特斯拉可能比現在的自行車都要便宜,地球人民人手一輛特斯拉不成問題,算上外星人也不成問題。
機器指令與流水線
實際上說 CPU 生產機器指令是不正確的,CPU 其實不是在生產機器指令而是在處理機器指令,生產機器指令的是編譯器,CPU需要處理機器指令以此來指揮整個計算機系統工作。
同生產一輛特斯拉需要四道工序一樣,處理一條機器指令大體上也可以分為四個步驟:取指、譯碼、執行、回寫,這幾個階段分別由特定的硬件來完成 (注意,真實 CPU 內部可能會將執行一條指令分解為數十個階段)。
怎么樣,是不是和超級工廠生產特斯拉沒什么區別,當今CPU用每秒處理數十億機器指令的能力驅動著智能手機好讓你流暢的刷公眾號、短視頻、刷微博、刷知乎,這里,流水線技術功不可沒。
當 if 遇到流水線
實際上 CPU 內部的流水線和現實中的并不完全一樣。程序員在代碼中編寫的 if 語句一般會被編譯器翻譯成一條跳轉指令,if 語句其實起到一種分支的作??用,如果條件成立則需要執行if內部的邏輯,否則不執行;因此跳轉指令會依賴自身的執行結果來決定到底要不要跳轉,這會對流水線產生影響。
有的同學可能不明白,這能產生什么影響呢?
現在,讓我們仔細觀察一下特斯拉流水線,你會發現當前一輛車還沒有完全制造完成時后一輛車就已經進入到流水線了。
對于CPU來說道理是一樣的,當一條跳轉指令還沒有完成時后面的指令就需要進入到流水線,因此問題來了:
跳轉指令需要依賴自身的執行結果來決定到底要不要跳轉,那么在跳轉指令沒有執行完的情況下 CPU 怎么知道后面哪個分支的指令能進入到流水線呢?
CPU 能預測未來嗎?
預測未來
對此 CPU 當然是不知道的。那么該怎么辦呢?很簡單,一個字,猜。
你沒有看錯,CPU 會猜一下 if 語句可能會走哪個分支,如果猜對了流水線照常繼續,如果猜錯了,對不起,流水線上已經執行的后續指令全部作廢,因此我們可以看到如果CPU猜錯了會有性能損耗。
現代 CPU 將“猜”的這個過程稱為分支預測。
當然,CPU 中的分支預測并不是簡單的拋硬幣式的隨機瞎猜,而且有特定策略,比如可能會基于執行跳轉指令的歷史去進行預測等等。
知道了分支預測就可以解釋文章開頭的問題了。
現在我們知道,程序員編寫的 if 語句對應的是跳轉指令:
if?(arr[i]?>=?256)?{sum?+=?arr[i];}CPU 在執行完跳轉指令之前必須決定后續哪個分支的指令會進入到流水線,猜對了流水線照常進行,猜錯了有性能損耗。
那么如果一個數組是有序的:
而如果一個數組是無序的:
你覺得哪種更好猜一些?
如果你給CPU一個無序數組,那么 Arr[i] 是否大于256 基本上就是隨機的,對于隨機事件,不要說CPU的分支預測,任何其它預測手段都將無效,否則這就不是隨機事件了。
如果 CPU 猜的不對,那么流水線上的后續指令將作廢,這就解釋了為什么處理有序數組要比處理無序數組性能好了,因為在數組有序的情況下,CPU 的分支預測幾乎不會猜錯,流水線上的指令不會被頻繁作廢。
這對程序員的啟示就是:如果你編寫了 if 語句,那么你最好讓 CPU 大概率能猜對。
有的同學看到這里,可能會覺得每一條 if 語句都性能低下,恨不得從此不再寫if else,真的是這樣嗎?
編寫 If else時需要注意什么
實際上如果你編寫的if語句沒有位于對性能要求很高的核心代碼部分,那么分支預測失敗這種問題無需關心。
實際上現代 CPU 的分支預測是很聰明的,對于非核心部分的if 語句分支預測失敗帶來的性能損失可以忽略不計。
但是對于文章開頭提到的代碼,程序的大部分時間都用在了 for 循環中,這時你就要注意了,當然前提還是這段代碼對時間要求非常嚴苛,否則你也沒必要為了這點性能去優化。
好奇的同學可能會問,如果給定的數組是無序的,那么上面提到的這段該怎么優化呢?
性能優化
實際上非常簡單,只需要移除 if 語句就可以,該怎么移除呢?
沒有 if 語句的話,那么 sum 每次都必須加上一個數,如果arr[i]比256大,那么 sum 加上差值,否則 sum 加 0即可,這樣就消除了if 判斷。
我們計算arr[i] - 256的值,并將其向右移動31位:
(arr[i]?-?256)?>>?31這樣得到的數不是0 (0x00000000),就是 -1 (0xffffffff),然后我們對其取反,再次與上 arr[i] 即可:
sum?+=?~((arr[i]?-?256)?>>?31)?&?arr[i];也就是說如果arr[i] - 256 大于0 的話那么差值會與上 0xffffffff,其結果就是保持不變,否則會與上0,其結果就是sum會加上0,這樣就不需要 if 判斷了。
利用位運算,即使數組是無序的也不會有性能問題,代價就是代碼可讀性會降低很多,這里,我們再一次看到天下沒有免費的午餐。
總結
雖然 CPU 體積很小,只有指甲那么大,但 CPU 可能是人類有史以來建造過的最復雜的東西,在這里實現了很多有趣的功能,程序員只有徹底理解 CPU 才能更好的利用這些功能編寫性能優異的程序。
希望這篇對大家理解 CPU 有所幫助。
2020-2021中國開發者調查報告重磅來襲,直接掃碼或微信搜索「CSDN」公眾號,后臺回復關鍵詞「開發者」,快速獲取完整的報告內容!
更多精彩推薦 ?如何部署一個Kubernetes集群?繼云計算巨頭失火后,微軟決定送數據中心去“泡澡”!?走過 30 年:銀行數據庫的下一步是國產化 點分享點收藏點點贊點在看 創作挑戰賽新人創作獎勵來咯,堅持創作打卡瓜分現金大獎總結
以上是生活随笔為你收集整理的发掘 CPU 与超级工厂的共性,程序员的心思你别猜的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 产业数字化升级进入深化期,腾讯智慧出行释
- 下一篇: 这 10 行比较字符串相等的代码给我整懵