先看再點贊,給本身一點思考的時間,微信搜索【 沉默王二】關注這個有顏值卻僞裝靠才華苟且的程序員。
本文 GitHub github.com/itwanger 已收錄,裏面還有我精心爲你準備的一線大廠面試題。
今天週日,沒什麼重要的事情要作,因而我早早的就醒來了。看了一會渡邊淳一的書,心裏逐漸感到平靜——心情不佳的時候,書好像是最好的藥物。心情平靜了,就須要作一些更有意義的事情——逛技術網站,學習精進。java
Stack Overflow 是我最喜歡逛的一個網站,它是我 Chrome 瀏覽器的第一個書籤。裏面有不少不少經典的問題,其中一些回答,剖析得深刻我心。就好比說這個:「爲何處理排序後的數組比沒有排序的快?」git
毫無疑問,直觀印象裏,排序後的數組處理起來就是要比沒有排序的快,甚至不須要理由,就好像咱們知道「夏天吃冰激凌就是爽,冬天穿羽絨服就是暖和」同樣。程序員
但本着「知其然知其因此然」的態度,咱們確實須要去搞清楚究竟是爲何?github
來看一段 Java 代碼:面試
/** * @author 沉默王二,一枚有趣的程序員 */ public class SortArrayFasterDemo { public static void main(String[] args) { // 聲明數組 int arraySize = 32768; int data[] = new int[arraySize]; Random rnd = new Random(0); for (int c = 0; c < arraySize; ++c) { data[c] = rnd.nextInt() % 256; } // !!! 排序後,比沒有排序要快 Arrays.sort(data); // 測試 long start = System.nanoTime(); long sum = 0; for (int i = 0; i < 100000; ++i) { // 循環 for (int c = 0; c < arraySize; ++c) { if (data[c] >= 128) { sum += data[c]; } } } System.out.println((System.nanoTime() - start) / 1000000000.0); System.out.println("sum = " + sum); } }
這段代碼很是簡單,我來解釋一下:數組
rnd.nextInt() % 256
將會產生一個餘數,餘數的絕對值在 0 到 256 之間,包括 0,不包括 256,多是負數;使用餘數對數組進行填充。Arrays.sort()
進行排序。System.nanoTime()
計算先後的時間差,精確到納秒級。我本機的環境是 Mac OS,內存 16 GB,CPU Intel Core i7,IDE 用的是 IntelliJ IDEA,排序後和未排序後的結果以下:瀏覽器
排序後:2.811633398
未排序:9.41434346微信
時間差仍是很明顯的,對吧?未排序的時候,等待結果的時候讓我有一種擔憂:何時結束啊?不會結束不了吧?dom
讀者朋友們有沒有玩過火炬之光啊?一款很是經典的單機遊戲,每個場景都有一副地圖,地圖上有不少分支,但只有一個分支能夠通往下一關;在沒有刷圖以前,地圖是模糊的,玩家並不知道哪一條分支是正確的。學習
若是僥倖跑的是一條正確的分支,那麼很快就能到達下一關;不然就要往回跑,尋找正確的那條分支,須要花費更多的時間,但同時也會收穫更多的經驗和聲望。
做爲一名玩過火炬之光好久的老玩家,幾乎每一幅地圖我都刷過不少次,刷的次數多了,地圖差很少就刻進了個人腦殼,即使是一開始地圖是模糊的,我也能憑藉經驗和直覺找到最正確的那條分支,就省了不少折返跑的時間。
讀者朋友們應該注意到了,上面的代碼中有一個 if 分支——if (data[c] >= 128)
,也就是說,若是數組中的值大於等於 128,則對其進行累加,不然跳過。
那這個代碼中的分支就好像火炬之光中的地圖分支,若是處理器可以像我同樣提早預判,那累加的操做就會快不少,對吧?
處理器的內部結構我是不懂的,但它應該和個人大腦是相似的,遇到 if 分支的時候也須要停下來,猜一猜,到底要不要繼續,若是每次都猜對,那顯然就不須要折返跑,浪費時間。
這就是傳說中的分支預測!
我須要刷不少次圖才能正確地預測地圖上的路線,處理器須要排序才能提升判斷的準確率。
計算機發展了這麼多年,已經變得很是很是聰明,對於條件的預測一般能達到 90% 以上的命中率。可是,若是分支是不可預測的,那處理器也無能爲力啊,對不對?
排序後花費的時間少,未排序花費的時間多,罪魁禍首就在 if 語句上。
if (data[c] >= 128) { sum += data[c]; }
數組中的值是均勻分佈的(-255 到 255 之間),至因而怎麼均勻分佈的,咱們暫且無論,反正由 Random 類負責。
爲了方便講解,咱們暫時忽略掉負數的那一部分,從 0 到 255 提及。
來看通過排序後的數據:
data[] = 0, 1, 2, 3, 4, ... 126, 127, 128, 129, 130, ... 250, 251, 252, ... branch = N N N N N ... N N T T T ... T T T ... = NNNNNNNNNNNN ... NNNNNNNTTTTTTTTT ... TTTTTTTTTT
N 是小於 128 的,將會被 if 條件過濾掉;T 是將要累加到 sum 中的值。
再來看未排序的數據:
data[] = 226, 185, 125, 158, 198, 144, 217, 79, 202, 118, 14, 150, 177, 182, 133, ... branch = T, T, N, T, T, T, T, N, T, N, N, T, T, T, N ... = TTNTTTTNTNNTTTN ...
徹底沒有辦法預測。
對比事後,就能發現,排序後的數據在遇到分支預測的時候,可以輕鬆地過濾掉 50% 的數據,對吧?是有規律可循的。
那假如說不想排序,又想節省時間,有沒有辦法呢?
若是你直接問個人話,我確定毫無辦法,兩手一攤,一副無奈臉。不過,Stack Overflow 以上帝視角給出了答案。
把:
if (data[c] >= 128) { sum += data[c]; }
更換爲:
int t = (data[c] - 128) >> 31; sum += ~t & data[c];
經過位運算消除了 if 分支(並不徹底等同),但我測試了一下,計算後的 sum 結果是相同的。
/** * @author 沉默王二,一枚有趣的程序員 */ public class SortArrayFasterDemo { public static void main(String[] args) { // 聲明數組 int arraySize = 32768; int data[] = new int[arraySize]; Random rnd = new Random(); for (int c = 0; c < arraySize; ++c) { data[c] = rnd.nextInt() % 256; } // 測試 long start = System.nanoTime(); long sum = 0; for (int i = 0; i < 100000; ++i) { // 循環 for (int c = 0; c < arraySize; ++c) { if (data[c] >= 128) { sum += data[c]; } } } System.out.println((System.nanoTime() - start) / 1000000000.0); System.out.println("sum = " + sum); // 測試 long start1 = System.nanoTime(); long sum1 = 0; for (int i = 0; i < 100000; ++i) { // 循環 for (int c = 0; c < arraySize; ++c) { int t = (data[c] - 128) >> 31; sum1 += ~t & data[c]; } } System.out.println((System.nanoTime() - start1) / 1000000000.0); System.out.println("sum1 = " + sum1); } }
輸出結果以下所示:
8.734795196 sum = 156871800000 1.596423307 sum1 = 156871800000
數組累加後的結果是相同的,但時間上仍然差得很是多,這說明時間確實耗在分支預測上——若是數組沒有排序的話。
最後,不得不說一句,大神級程序員不愧是大神級程序員,懂得位運算的程序員就是屌。
建議還在讀大學的讀者朋友多讀一讀《計算機操做系統原理》這種涉及到底層的書,對成爲一名優秀的程序員頗有幫助。畢竟大學期間,學習時間充分,社會壓力小,可以作到心無旁騖,加油!
我是沉默王二,一枚有顏值卻僞裝靠才華苟且的程序員。關注便可提高學習效率,別忘了三連啊,點贊、收藏、留言,我不挑,奧利給🌹。
注:若是文章有任何問題,歡迎絕不留情地指正。
若是你以爲文章對你有些幫助,歡迎微信搜索「沉默王二」第一時間閱讀;本文 GitHub github.com/itwanger 已收錄,歡迎 star。