爲何處理排序數組比未排序數組快

    今天在羣裏看到一個有意思的問題——爲何處理排序數組比處理沒有排序的數組要快,這個問題來源於 StackoverFlow,雖然我看到代碼略微知道緣由,可是模模糊糊不夠清晰,搜了不少博客也講的不夠明白,因此就本身來總結了。java

    首先來看一下問題,下面是很簡單的一段代碼,隨機生成一些數字,對其中大於 128 的元素求和,記錄並打印求和所用時間。git

import java.util.Arrays;
import java.util.Random;

public class Main {
    public static void main(String[] args) {
        // Generate data
        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;

        // !!! With this, the next loop runs faster
        Arrays.sort(data);

        // Test
        long start = System.nanoTime();
        long sum = 0;

        for (int i = 0; i < 100000; ++i)
        {
            // Primary loop
            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);
    }
}
複製代碼

    個人運行結果:分別在對數組排序和不排序的前提下測試,在不排序時所用的時間比先排好序所用時間平均要多 10 ms。這不是巧合,而是必然的結果。github

    問題就出在那個if判斷上面,在舊文順序、條件、循環語句的底層解釋中其實已經提到了形成這種結果的緣由,只是舊文中沒有拿出具體的例子來講明。數組

    爲了把這個問題搞明白,須要先對流水線有必定的瞭解。計算機是指令流驅動的,執行的是一個一個的指令,而執行一條指令,又要通過取指、譯碼、執行、訪存、寫回、更新六個階段(不一樣的劃分方式所包含的階段不同)。微信

    六個階段使用的硬件基本是不同的,若是一條指令執行完再去執行另外一條指令,那麼在這段時間裏會有不少硬件處於空閒狀態,要使計算機的速度變快,那麼就不能讓硬件停下來,因此有了流水線技術。dom

    流水線技術經過將指令重疊來實現幾條指令並行處理,下圖表示的是三階段指令時序,即把一個指令分爲三個階段。在第一條指令的 B 階段,A 階段相關的硬件是空閒的,因而能夠將第二條指令的 A 階段提早操做。oop

image

    很明顯,這種設計大幅提升了指令運行的效率,聰明的你可能發現問題了,要是不知道下一條指令是什麼怎麼辦,那提早的階段也就白乾了,那樣流水線不就失效了?沒錯,這就是致使開篇問題的緣由。post

    讓流水線出問題的狀況有三種:一、數據相關,後一條指令須要用到前一條指令的運算結果;二、控制相關,好比無條件跳轉,跳轉的地址須要在譯碼階段才能知道,因此跳轉以後已經被取出的指令流水就須要清空;三、結構相關,因爲一些指令須要的時鐘週期長(好比浮點運算等),長時間佔用硬件,致使以後的指令沒法進入譯碼等階段,即它們在爭用同一套硬件。測試

    代碼中的if (data[c] >= 128)翻譯成機器語言就是跳轉指令,處理器事先並不知道要跳轉到哪一個分支,那難道就等知道了纔開始下一條指令的取指工做嗎?處理器選擇了僞裝知道會跳轉到哪一個分支(不是謙虛,是真的僞裝知道),若是猜中了是運氣好,而沒有猜中那就浪費一點時間從新來幹。this

    沒有排序的數組,元素是隨機排列的,每次data[c] >= 128的結果也是隨機的,前面的經驗就不可參考,因此下一次執行到這裏理論上仍是會有 50% 的可能會猜錯,猜錯了確定就須要花時間來修改犯下的錯誤,天然就會浪費更多的時間。

    對於排好序的數組,開始幾回也須要靠猜,可是猜着猜着發現有規律啊,每次都是往同一個分支跳轉,因此之後基本上每次都能猜中,當遍歷到與 128 分界的地方,纔會出現猜不中的狀況,可是猜幾回以後,發現這又有規律啊,每次都是朝着另一個相同分支走的。

    雖然都會猜錯,可是在排好序的狀況下猜錯的概率遠遠小於未排序時的概率,最終呈現的結果就是處理排序數組比未排序數組快,其緣由就是流水線發生了大量的控制相關現象,下面通俗一點,加深一下理解。

image

    遠在他方心儀多年的姑娘忽然告訴你,其實她也喜歡你,激動的你三天三夜睡不着覺,決定開車前往她的城市,要和她待在一塊兒,可是要去的路上有不少不少岔路,你只能使用的某某地圖導航,做爲老司機而且懷着立馬要見到愛人心情的你,開車超快,什麼樣罰單都不在意了。

    地圖定位已經跟不上你的速度了,爲了儘快到達,遇到岔路你都是隨機選一條路前進,遺憾的是,本身的選擇不必定對(咱們假設高速能夠回退),走錯路了就要從新回到分岔點,這就對應着未排序的狀況。

    如今岔路是有規律的,告訴你開始一直朝着一邊走,到某個地點後會一直朝着另外一邊走,你只須要花點時間去探索一下開始朝左邊仍是右邊,到了中間哪一個地點會改變方向就能夠了,相比之下就能節省很多時間了,儘快見到本身的愛人,這對應着排好序的狀況。

    最後的故事改編自兩我的的現實生活,一位是本身最好的朋友之一,談戀愛開心的睡不着覺;另外一位是微信上的一位好友,爲了對方從北京裸辭飛到了深圳。

相關文章
相關標籤/搜索