《拉鉤課程 - 重學操作系統 - 計算機組成原理》

1、芯片是怎麼工作的呢?電能供給給芯片,芯片中的一種電子元件晶振(也就是石英晶體)通電後產生震盪,震盪會產生頻率穩定的脈衝信號。通常這是一種高頻的脈衝信號,每秒可達百萬次。然後,我們通過諧振效應發放這個信號,形成方波。再通過電子元件調整這種脈衝的頻率,把脈衝信號轉換爲我們需要的頻率,這就形成了驅動芯片工作的時鐘信號。這種信號的頻率,我們也稱作芯片的時鐘頻率。最後,時鐘信號驅動着芯片工作,就像人體的脈搏一樣,每一次脈衝到來,都讓芯片的狀態發生一次變化,用這種方法,最終存儲器中的指令被一行行執行。

2、最早在 19 世紀初,德國著名數學家希爾伯特提出:這個世界可以建立一套完善的公理體系,由少數幾個公理出發,推導出所有的定理和推論。後來哥德爾提出了不完備性定理,反駁了這種觀點:即便在完善的公理體系中仍然可以找到不能被證明也不能被證僞的命題。

3、圖靈發現如果一個問題是可計算的,那麼它的解決方案就必須可以被具化成一條條的指令,也就是可以使用圖靈機處理。因此,不能使用圖靈機處理的問題,都是不可計算的問題。

4、不可計算問題,比如 “素數是不是有無窮多個?”儘管我們可以通過有限的步驟計算出下一個素數,但是,我們還是不能回答“素數是不是有無窮多個”這樣的問題。因爲要回答這樣的問題,我們會不停地尋找下一個素數。如果素數是無窮的,那麼我們的計算就是無窮無盡的,所以這樣的問題不可計算。

5、不可計算問題,比如停機問題。我們無法實現用一個通用程序去判斷另一個程序是否會停止。比如你用運行這段程序來檢查一個程序是否會停止時,你會發現不能因爲這個程序執行了 1 天,就判定它不會停止,也不能因爲這個程序執行了 10 年,從而得出它不會停止的結論。

6、可計算問題,其計算開銷又分爲時間複雜度和空間複雜度。

7、在所有可以計算的問題中,像 O(N^1000) 的問題,雖然現在的計算能力不夠,但是相信在遙遠的未來,我們會擁有能力解決。這種我們有能力解決的問題,統稱爲多項式時間(Polynomial time)問題。

8、另外,還有一類問題複雜度本身也是指數形式的問題,比如 O(2^N)的問題。這類型的問題隨着規模 N 上升,時間開銷的增長速度和人類計算能力增長速度持平甚至更快。因此雖然這類問題可以計算,但是當 N 較大時,因爲計算能力不足,最終結果依然無法被解決。我們記爲 NP 問題。

9、圖靈機在計算機科學方面有兩個巨大的貢獻:

  • 它清楚地定義了計算機能力的邊界,也就是可計算理論;
  • 它定義了計算機由哪些部分組成,程序又是如何執行的;

10、圖靈機的內部構造:

  • 圖靈機擁有一條無限長的紙帶,紙帶上是一個格子挨着一個格子,格子中可以寫字符,你可以把紙帶看作內存,而這些字符可以看作是內存中的數據或者程序;
  • 圖靈機有一個讀寫頭,讀寫頭可以讀取任意格子上的字符,也可以改寫任意格子的字符;
  • 讀寫頭上面的盒子裏是一些精密的零件,包括圖靈機的存儲、控制單元和運算單元;

11、馮諾依曼模型遵循了圖靈機的設計,並提出了用電子元件構造計算機,約定了用二進制進行計算和存儲,並且將計算機結構分爲以下五個部分:

  • 輸入設備
  • 輸出設備
  • 內存
  • 中央處理器
  • 總線

12、馮·諾依曼體系結構的要點是:計算機的數制採用二進制。計算機應該按照程序順序執行。它採用存儲程序方式,指令和數據不加區別,混合存儲在同一個存儲器中。數據和程序在內存中是沒有區別的,它們都是內存中的數據。當 EIP 指針指向哪,CPU 就加載哪段內存中的數據。如果是不正確的指令格式,CPU 就會發生錯誤中斷。

13、馮諾依曼模型中 CPU 負責控制和計算。爲了方便計算較大的數值,CPU 每次可以計算多個字節的數據。

  • 如果 CPU 每次可以計算 4 個 byte(寄存器),那麼我們稱作 32 位 CPU;
  • 如果 CPU 每次可以計算 8 個 byte(寄存器),那麼我們稱作 64 位 CPU;

14、64 位的 CPU 和 32 位比較有哪些優勢?

  • 64 位 CPU 可以執行更大數字的運算,這個優勢在普通應用上不明顯,但是對於數值計算較多的應用就非常明顯;
  • 64 位 CPU 可以尋址更大的內存空間,但是也會受到地址總線條數的限制。比如和 64 位 CPU 配套工作的地址總線只有 40 條,那麼可以尋址的範圍就只有 1T,也就是 2^40。

15、CPU 離內存太遠,需要一種離自己近的存儲來存儲將要被計算的數字,這種存儲就是寄存器,寄存器就在 CPU 裏,離控制單元和邏輯運算單元非常近,因此速度很快。

16、CPU 和內存以及其他設備之間,也需要通信,因此我們用一種特殊的設備進行控制,就是總線。總線分成 3 種:

  • 地址總線。專門用來指定 CPU 將要操作的內存地址;
  • 數據總線。用來讀寫內存中的數據。
  • 控制總線。用來發送和接收關鍵信號,比如後面我們會學到的中斷信號,還有設備復位、就緒等信號,都是通過控制總線傳輸。

17、CPU 的指令週期:

  • 首先 CPU 通過 PC 指針讀取對應內存地址的指令,我們將這個步驟叫做 Fetch,就是獲取的意思。
  • CPU 對指令進行解碼。我們將這個部分叫做 Decode。
  • CPU 執行指令,我們將這個部分叫做 execute。
  • CPU 將結果存回寄存器或者將寄存器存入內存,我們將這個步驟叫做 Store。

18、一段代碼 a = 11 + 15,它是怎麼被 CPU 執行的呢?首先,這是一段高級語言,要被先編譯成機器能識別的低級指令,這些指令和數據會被存儲到專門的區域;然後 PC 會指向最開始的指令,依次執行:

  • 0x200 位置的 load 指令將地址 0x100 中的數據 11 導入寄存器 R0;
  • 0x204 位置的 load 指令將地址 0x104 中的數據 15 導入寄存器 R1;
  • 0x208 位置的 add 指令將寄存器 R0 和 R1 中的值相加,存入寄存器 R2;
  • 0x20c 位置的 store 指令將寄存器 R2 中的值存回數據區域中的 0x1108 位置。

19、CPU 指令從功能上來劃分,大概可以分爲以下 5 類:

  • I/O 類型的指令,比如處理和內存間數據交換的指令 store/load 等;再比如將一個內存地址的數據轉移到另一個內存地址的 mov 指令。
  • 計算類型的指令,最多隻能處理兩個寄存器,比如加減乘除、位運算、比較大小等。
  • 跳轉類型的指令,用處就是修改 PC 指針。比如編程中大家經常會遇到需要條件判斷+跳轉的邏輯,比如 if-else,swtich-case、函數調用等。
  • 信號類型的指令,比如發送中斷的指令 trap。
  • 閒置 CPU 的指令 nop,一般 CPU 都有這樣一條指令,執行後 CPU 會空轉一個週期。

20、CPU 是用石英晶體產生的脈衝轉化爲時鐘信號驅動的,每一次時鐘信號高低電平的轉換就是一個週期,我們稱爲時鐘週期。CPU 的主頻,說的就是時鐘信號的頻率。比如一個 1GHz 的 CPU,說的是時鐘信號的頻率是 1G。

21、平時你編程做的事情,用機器指令也能做,所以從計算能力上來說它們是等價的,最終這種計算能力又和圖靈機是等價的。如果一個語言的能力和圖靈機等價,我們就說這個語言是圖靈完備的語言。現在市面上的絕大多數語言都是圖靈完備的語言,但也有一些不是,比如 HTML、正則表達式和 SQL 等。

22、我們把存儲器分成幾個級別:

  1. 寄存器:訪問速度非常快,一般要求在半個 CPU 時鐘週期內完成,讀寫數量通常在幾十到幾百之間,每個寄存器可以用來存儲一定字節(byte)的數據(32 位 CPU 存儲 4 個字節;64 位 CPU 存儲 8 個字節)。

  2. L1-Cache:L1- 緩存在 CPU 中,相比寄存器,雖然它的位置距離 CPU 核心更遠,但造價更低。通常 L1-Cache 大小在幾十 Kb 到幾百 Kb 不等,讀寫速度在 2~4 個 CPU 時鐘週期

  3. L2-Cache:L2- 緩存也在 CPU 中,位置比 L1- 緩存距離 CPU 核心更遠。它的大小比 L1-Cache 更大,具體大小要看 CPU 型號,有 2M 的,也有更小或者更大的,速度在 10~20 個 CPU 週期

  4. L3-Cache:L3- 緩存同樣在 CPU 中,位置比 L2- 緩存距離 CPU 核心更遠。大小通常比 L2-Cache 更大,讀寫速度在 20~60 個 CPU 週期。L3 緩存大小也是看型號的,比如 i9 CPU 有 512KB L1 Cache;有 2MB L2 Cache; 有16MB L3 Cache。

  5. 內存:內存的主要材料是半導體硅,是插在主板上工作的。因爲它的位置距離 CPU 有一段距離,所以需要用總線和 CPU 連接。因爲內存有了獨立的空間,所以體積更大,造價也比上面提到的存儲器低得多。現在有的個人電腦上的內存是 16G,但有些服務器的內存可以到幾個 T。內存速度大概在 200~300 個 CPU 週期之間。

  6. SSD 和硬盤:SSD 也叫固態硬盤,結構和內存類似,但是它的優點在於斷電後數據還在。內存、寄存器、緩存斷電後數據就消失了。內存的讀寫速度比 SSD 大概快 10~1000 倍。以前還有一種物理讀寫的磁盤,我們也叫作硬盤,它的速度比內存慢 100W 倍左右。因爲它的速度太慢,現在已經逐漸被 SSD 替代。

23、當 CPU 需要內存中某個數據的時候,如果寄存器中有這個數據,我們可以直接使用;如果寄存器中沒有這個數據,我們就要先查詢 L1 緩存;L1 中沒有,再查詢 L2 緩存;L2 中沒有再查詢 L3 緩存;L3 中沒有,再去內存中拿。

24、CPU 會把內存中的指令預讀幾十條或者上百條到讀寫速度較快的 L1- 緩存中,因爲 L1- 緩存的讀寫速度只有 2~4 個時鐘週期,是可以跟上 CPU 的執行速度的。這裏又產生了另一個問題:如果數據和指令都存儲在 L1- 緩存中,如果數據緩存覆蓋了指令緩存,就會產生非常嚴重的後果。因此,L1- 緩存通常會分成兩個區域,一個是指令區,一個是數據區。(L2/L3 不需要劃分指令區和數據區,因爲它們不需要協助處理指令預讀的事情)

25、據統計,L1 緩存的命中率在 80% 左右,L1/L2/L3 加起來的命中率在 95% 左右。因此,CPU 緩存的設計還是相當合理的。只有 5% 的內存讀取會穿透到內存,95% 都能讀取到緩存。 這也是爲什麼程序語言逐漸取消了讓程序員操作寄存器的語法,因爲緩存保證了很高的命中率,多餘的優化意義不大,而且很容易出錯。

26、SSD、內存和 L1 Cache 相比速度差多少倍?因爲內存比 SSD 快 10~1000 倍,L1 Cache 比內存快 100 倍左右。因此 L1 Cache 比 SSD 快了 1000~100000 倍。所以你有沒有發現 SSD 的潛力很大,好的 SSD 已經接近內存了,只不過造價還略高。這個問題告訴我們,不同的存儲器之間性能差距很大,構造存儲器分級很有意義,分級的目的是要構造緩存體系。

27、假設有一個二維數組,總共有 1M 個條目,如果我們要遍歷這個二維數組,應該逐行遍歷還是逐列遍歷?

【解析】 二維數組本質還是 1 維數組。只不過進行了腳標運算。比如說一個 N 行 M 列的數組,第 y 行第 x 列的座標是: x + y*M。因此當行座標增加時,內存空間是跳躍的。列座標增加時,內存空間是連續的。

當 CPU 遍歷二維數組的時候,會先從 CPU 緩存中取數據。關鍵因素在於現在的 CPU 設計不是每次讀取一個內存地址,而是讀取每次讀取相鄰的多個內存地址(內存速度 200~300 CPU 週期,預讀提升效率)。所以這相當於機器和人的約定,如果程序員不按照這個約定,就無法利用預讀的優勢。

另一方面當讀取內存地址跳躍較大的時候,會觸發內存的頁面置換。

28、怎麼用非遞歸算法實現斐波那契數列?

public class Call {

    public static void main(String[] args) {
        int fib = fib(4);
        System.out.println(fib);
        int fib2 = fib2(4);
        System.out.println(fib2);
    }

    static int fib(int n) {
        if (n == 1 || n == 2) {
            return n;
        }
        return fib(n - 1) + fib(n - 2);
    }

    private static int fib2(int n) {
        if (n == 1 || n == 2) {
            return n;
        }
        //初始化數據
        int[] stack = new int[n];
        int point = n - 3;
        stack[n - 1] = 1;
        stack[n - 2] = 2;
        //數組模擬出棧入棧
        while (point >= 0) {
            stack[point] = stack[point + 1] + stack[point + 2];
            point--;
        }
        return stack[0];
    }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章