<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          究竟!為什么處理排序后的數(shù)組比沒有排序的快?想過沒有?

          共 1820字,需瀏覽 4分鐘

           ·

          2020-08-18 02:10

          今天周日,沒什么重要的事情要做,于是我早早的就醒來了??戳艘粫蛇叴疽坏臅?,內(nèi)心逐漸感到平靜——心情不佳的時候,書好像是最好的藥物。心情平靜了,就需要做一些更有意義的事情——逛技術(shù)網(wǎng)站,學(xué)習(xí)精進(jìn)。

          Stack Overflow 是我最喜歡逛的一個網(wǎng)站,它是我 Chrome 瀏覽器的第一個書簽。

          里面有很多很多經(jīng)典的問題,其中一些回答,剖析得深入我心。就比如說這個:“為什么處理排序后的數(shù)組比沒有排序的快?”

          毫無疑問,直觀印象里,排序后的數(shù)組處理起來就是要比沒有排序的快,甚至不需要理由,就好像我們知道“夏天吃冰激凌就是爽,冬天穿羽絨服就是暖和”一樣。

          但本著“知其然知其所以然”的態(tài)度,我們確實需要去搞清楚到底是為什么?

          來看一段 Java 代碼:

          /**
          ?*?@author?沉默王二,一枚有趣的程序員
          ?*/

          public?class?SortArrayFasterDemo?{
          ????public?static?void?main(String[]?args)?{
          ????????//?聲明數(shù)組
          ????????int?arraySize?=?32768;
          ????????int?data[]?=?new?int[arraySize];

          ????????Random?rnd?=?new?Random(0);
          ????????for?(int?c?=?0;?c?????????????data[c]?=?rnd.nextInt()?%?256;
          ????????}

          ????????//?!!!?排序后,比沒有排序要快
          ????????Arrays.sort(data);

          ????????//?測試
          ????????long?start?=?System.nanoTime();
          ????????long?sum?=?0;

          ????????for?(int?i?=?0;?i?100000;?++i)
          ????????{
          ????????????//?循環(huán)
          ????????????for?(int?c?=?0;?c?????????????{
          ????????????????if?(data[c]?>=?128)?{
          ????????????????????sum?+=?data[c];
          ????????????????}
          ????????????}
          ????????}

          ????????System.out.println((System.nanoTime()?-?start)?/?1000000000.0);
          ????????System.out.println("sum?=?"?+?sum);
          ????}
          }

          這段代碼非常簡單,我來解釋一下:

          • 聲明一個指定長度(32768)的數(shù)組。

          • 聲明一個 Random 隨機(jī)數(shù)對象,種子是 0;rnd.nextInt() % 256 將會產(chǎn)生一個余數(shù),余數(shù)的絕對值在 0 到 256 之間,包括 0,不包括 256,可能是負(fù)數(shù);使用余數(shù)對數(shù)組進(jìn)行填充。

          • 使用 Arrays.sort() 進(jìn)行排序。

          • 通過 for 循環(huán)嵌套計算數(shù)組累加后的結(jié)果,并通過 System.nanoTime() 計算前后的時間差,精確到納秒級。

          我本機(jī)的環(huán)境是 Mac OS,內(nèi)存 16 GB,CPU ?Intel Core i7,IDE 用的是 IntelliJ IDEA,排序后和未排序后的結(jié)果如下:

          排序后:2.811633398
          未排序:9.41434346

          時間差還是很明顯的,對吧?未排序的時候,等待結(jié)果的時候讓我有一種擔(dān)心:什么時候結(jié)束?。坎粫Y(jié)束不了吧?

          讀者朋友們有沒有玩過火炬之光???一款非常經(jīng)典的單機(jī)游戲,每一個場景都有一副地圖,地圖上有很多分支,但只有一個分支可以通往下一關(guān);在沒有刷圖之前,地圖是模糊的,玩家并不知道哪一條分支是正確的。

          如果僥幸跑的是一條正確的分支,那么很快就能到達(dá)下一關(guān);否則就要往回跑,尋找正確的那條分支,需要花費(fèi)更多的時間,但同時也會收獲更多的經(jīng)驗和聲望。

          作為一名玩過火炬之光很久的老玩家,幾乎每一幅地圖我都刷過很多次,刷的次數(shù)多了,地圖差不多就刻進(jìn)了我的腦袋,即便是一開始地圖是模糊的,我也能憑借經(jīng)驗和直覺找到最正確的那條分支,就省了很多折返跑的時間。

          讀者朋友們應(yīng)該注意到了,上面的代碼中有一個 if 分支——if (data[c] >= 128),也就是說,如果數(shù)組中的值大于等于 128,則對其進(jìn)行累加,否則跳過。

          那這個代碼中的分支就好像火炬之光中的地圖分支,如果處理器能夠像我一樣提前預(yù)判,那累加的操作就會快很多,對吧?

          處理器的內(nèi)部結(jié)構(gòu)我是不懂的,但它應(yīng)該和我的大腦是類似的,遇到 if 分支的時候也需要停下來,猜一猜,到底要不要繼續(xù),如果每次都猜對,那顯然就不需要折返跑,浪費(fèi)時間。

          這就是傳說中的分支預(yù)測!

          我需要刷很多次圖才能正確地預(yù)測地圖上的路線,處理器需要排序才能提高判斷的準(zhǔn)確率。

          計算機(jī)發(fā)展了這么多年,已經(jīng)變得非常非常聰明,對于條件的預(yù)測通常能達(dá)到 90% 以上的命中率。但是,如果分支是不可預(yù)測的,那處理器也無能為力啊,對不對?

          排序后花費(fèi)的時間少,未排序花費(fèi)的時間多,罪魁禍?zhǔn)拙驮?if 語句上。

          if?(data[c]?>=?128)?{
          ????sum?+=?data[c];
          }

          數(shù)組中的值是均勻分布的(-255 到 255 之間),至于是怎么均勻分布的,我們暫且不管,反正由 Random 類負(fù)責(zé)。

          為了方便講解,我們暫時忽略掉負(fù)數(shù)的那一部分,從 0 到 255 說起。

          來看經(jīng)過排序后的數(shù)據(jù):

          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 中的值。

          再來看未排序的數(shù)據(jù):

          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?...???

          完全沒有辦法預(yù)測。

          對比過后,就能發(fā)現(xiàn),排序后的數(shù)據(jù)在遇到分支預(yù)測的時候,能夠輕松地過濾掉 50% 的數(shù)據(jù),對吧?是有規(guī)律可循的。

          那假如說不想排序,又想節(jié)省時間,有沒有辦法呢?

          如果你直接問我的話,我肯定毫無辦法,兩手一攤,一副無奈臉。不過,Stack Overflow 以上帝視角給出了答案。

          把:

          if?(data[c]?>=?128)?{
          ????sum?+=?data[c];
          }

          更換為:

          int?t?=?(data[c]?-?128)?>>?31;
          sum?+=?~t?&?data[c];

          通過位運(yùn)算消除了 if 分支(并不完全等同),但我測試了一下,計算后的 sum 結(jié)果是相同的。

          /**
          ?*?@author?沉默王二,一枚有趣的程序員
          ?*/

          public?class?SortArrayFasterDemo?{
          ????public?static?void?main(String[]?args)?{
          ????????//?聲明數(shù)組
          ????????int?arraySize?=?32768;
          ????????int?data[]?=?new?int[arraySize];

          ????????Random?rnd?=?new?Random();
          ????????for?(int?c?=?0;?c?????????????data[c]?=?rnd.nextInt()?%?256;
          ????????}

          ????????//?測試
          ????????long?start?=?System.nanoTime();
          ????????long?sum?=?0;

          ????????for?(int?i?=?0;?i?100000;?++i)
          ????????{
          ????????????//?循環(huán)
          ????????????for?(int?c?=?0;?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)
          ????????{
          ????????????//?循環(huán)
          ????????????for?(int?c?=?0;?c?????????????{
          ????????????????int?t?=?(data[c]?-?128)?>>?31;
          ????????????????sum1?+=?~t?&?data[c];
          ????????????}
          ????????}

          ????????System.out.println((System.nanoTime()?-?start1)?/?1000000000.0);
          ????????System.out.println("sum1?=?"?+?sum1);
          ????}
          }

          輸出結(jié)果如下所示:

          8.734795196
          sum?=?156871800000
          1.596423307
          sum1?=?156871800000

          數(shù)組累加后的結(jié)果是相同的,但時間上仍然差得非常多,這說明時間確實耗在分支預(yù)測上——如果數(shù)組沒有排序的話。

          最后,不得不說一句,大神級程序員不愧是大神級程序員,懂得位運(yùn)算的程序員就是屌。

          建議還在讀大學(xué)的讀者朋友多讀一讀《計算機(jī)操作系統(tǒng)原理》這種涉及到底層的書,對成為一名優(yōu)秀的程序員很有幫助。畢竟大學(xué)期間,學(xué)習(xí)時間充分,社會壓力小,能夠做到心無旁騖,加油!


          PS:好了,好了,周日又到了二哥給大家送福利的時候了。北京大學(xué)出版社的《 Java 高并發(fā)編程指南》,送給讀者朋友們(周二發(fā)文時公布中獎名單)。

          ?


          可以點(diǎn)擊下面的鏈接直接購買。
          也可以通過下面的方式白嫖,就看你運(yùn)氣了。

          掃描下面的二維碼回復(fù)?高并發(fā)?,
          第 1,50,100,150,200 名獲得該獎勵



          瀏覽 25
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  丰满老妇高潮一级A片免费看 | 18日本xxxxx | 视频在线播放一区二区 | 狠狠操狠狠操狠狠操 | 夜色大香蕉视频网 |