CS:APP 電腦系統:程式設計師的角度 第一章筆記
這是來自卡內基梅隆大學的優質課程,對底層系統的理解越深,對程式設計師來說只有百利而無一害。雖然一時半刻看不到有什麼成效,但未來的某些日子一定會覺得當初學了這些真是太好了
首先從hello world
開始
以C
語言來說,讓寫好的以下程式碼執行,需要經過如下步驟
多方合作最終產生可執行的program
為什麼要懂編譯的運作過程?
- 優化程式效能: 尤其是
C
語言這種的古老語言,需要懂得編譯的過程是怎麼運作的才能使我們做更好的選擇。如今Java
以及Python
等新的語言都有一群高手在底層做各種優化,讓寫程式的我們可以免除很多麻煩與顧慮 - 理解
link-time errors
: 當程式可以使用之前,需要把各個需要的library link
起來 - 避免安全漏洞
以上這些都會在未來的章節詳細介紹
系統常見的硬體佈局
先來大致看過就好,未來的章節會詳細說
Buses (匯流排)
- 為部件傳遞
bytes
訊息 - 通常傳遞固定大小,稱為
words
- 不同系統的
words
可能有不同的大小 - 通常
32
位系統使用4 bytes
,64
位使用8 bytes words
I/O Devices
- 系統連接外部世界的方式,例如滑鼠鍵盤
- 通過
controller
或是adapter
連接至I/O bus
(controller
是晶片組在主板上或是設備本身;而adapter
是可插入主板插槽的card
。他們的目的都是為了能夠在bus
與device
之間傳遞訊息)
Main memory (主記憶體)
- 當處理器執行該程式時,存放程式執行時的臨時資料,包含程式以及其操控的數據
- 通常包含一坨
Dynamic random access memory (DRAM)
晶片 - 邏輯上,記憶體是一串
Bytes
陣列,每個陣列都有獨一無二的位址
處理器
CPU
interprets or executes
在主記憶體中的指令- 核心是
word-size storage device
(或稱register
),名為program counter (PC)
- 任何時候,
PC
都指向在主記憶體中的machine-language instructions
- 只要系統運作中,處理器永遠都在執行
PC
所指向的指令,完成後更新PC
指向下一個指令(指令不一定是記憶體中的連續下一個指令) register file
是一個小的儲存裝置,裡面包含一坨word-size registers
ALU (Arithmetic/logic unit)
計算新的數據和位址- 以下是幾個常見的
CPU operation
Load
: 從主記憶體複製byte
或是word
到一個register
,覆蓋該register
先前值Store
: 從register
複製byte
或是word
到主記憶體的某個位置,覆蓋該位置的值Operate
: 複製兩個registers
的內容到ALU
,對兩個words
執行arithmetic operation
,然後儲存結果到一個register
,覆蓋register
的先前值Jump
: 提取出指令本身,然後複製該word
到Program counter
,覆蓋PC
先前值
- 雖然架構簡單,但是現代的處理器使用複雜許多的機制來加速程式執行時間,詳細在之後章節會討論
執行hello
程式
當我們在shell
輸入./hello
的時候,這幾個字會通過controller->I/O bridge->Bus interface->register->最後再到main memory
,像下圖所示
當按下enter
鍵的時候,shell
就知道我們已經輸入完畢,接著就會自動執行hello
這個程式,然後把其指令以及數據從硬碟複製到主記憶體,數據包含hello, world\n
就會最終被印出來
期間,數據從硬碟到主記憶體是使用direct memory access (DMA)
技術,不用經過處理器,像Figure 1.6
所示
最終印出時,指令會從記憶體複製hello, world\n
的bytes
到register file
最後再到display device
,如Figure 1.7
Cache (快取)大有用處
綜上所述,可以發現系統花很多時間在移動資訊,例如為了加載程式,需要把程式複製到主記憶體;為了處理器可以執行程式,指令需要從主記憶體被複製到處理器等等。以程式設計師的角度來看,這些無疑都是為”real work”增加負擔。因此,系統設計師的主要目標就是要讓這些複製的操作盡可能的變快
物理定律造成越大容量的設備讀寫速度越慢,但越快的設備同樣的容量價格越高昂,例如:
disk drive
容量比主記憶體容量大一千倍,但處理器可能需要一千萬倍的時間去讀取disk drive
,相對於主記憶體
隨著半導體的進步,處理器與記憶體之間的速度差異逐年上升,簡單來說就是讓處理器變快比讓記憶體變快更加簡單
為了應對這個巨大的processor-memory gap
,系統設計師引入了cache memory
,又小又快,存放著處理器短期內可能會需要的資料,如Figure 1.9
,越上層的儲存設備存取速度越快
容量大小: L1 > L2 > L3
- 他們都是
static random access memory (SRAM)
技術的產物 L3
基本上存在於高性能電腦locality
是提升性能的關鍵,如何讓最常用的資料儲存在cache
中是核心Figure 1.9
,每一層都是下一層的cache
作業系統掌管硬體
- 回到
hello
程式,從頭到尾,其實程式都沒有直接存取滑鼠、鍵盤、主記憶體和螢幕等等的設備,全都是通過作業系統來達成溝通,如Figure 1.10
所示,OS
介於程式與硬體之間
作業系統這樣做有兩個目的:
- 保護硬體
- 提供應用程式一個簡單且統一的方式去操控各式各樣的硬體設備
為此兩個目的,OS
用抽象化來達成,就像Figure 1.11
Files
是I/O devices
的抽象virtual memory
->main memory
processes
->processor
Processes
當我們在執行hello
這個程式的時候彷彿沒有被打斷,整個系統都在為了執行它而工作,這個就是process
的厲害之處,因為實際上是有多個processes
同時執行。而通常processes
會比CPU
還多,所以沒辦法一個CPU
執行一個process
- 單
CPU
可以通過context switching
的方式來切換process
以達到多工處理的功能 context swtiching
會儲存當前process
的context
然後restore the context of the new process
,像Figure 1.12
- 接下來的幾段章節會著重討論只有單
CPU
的uniprocessor system
當我們在shell process
輸入完指令按下enter
,shell
會呼叫系統,讓OS
儲存shell
的context
,建立hello process
還有其context
,讓hello process
獲得控制權,結束後再restore shell context
Kernel是OS
的一部分,常駐在記憶體中,當一個程式需要OS
的協助的時候,例如讀取和寫入檔案,會執行system call
指令把控制權交還給kernel
,執行完操作後再交還給原本的程式。Kernel本身不是一個process,他是一坨code
和資料結構被系統用來管理所有的processes
Threads (執行緒)
- 現代系統中一個
process
常常包含很多thread
- 越發重要,因為網路伺服器需要併發效能
- 在
threads
之間傳遞訊息比processes
之間簡單得多 - 通常比
processes
更有效率
Virtual Memory (虛擬記憶體)
- 讓
process
以為自己有main memory
的專屬使用權 - 對每個
process
來說,memory
都長一樣,名為virtual address space
,如Figure 1.13
所示
- 最上層是給
OS
對每個process
的通用data
和code
的空間 - 往下層的都是
process
所定義的code
和data
- 地址由下往上遞增
Files
- 一大串
bytes
,就這樣 - 每個
I/O
設備包含硬碟、鍵盤、螢幕和網路等等都被模擬成file
,系統中的input
和output
都是以寫入和讀取檔案來執行
用網路來和其他系統溝通
- 網路可以被視為另一個
I/O
設備
本章結語和重點概念
Amdahl’s Law
Gene Amdahl
- 代表處理器並行運算之後效率提升的能力
- 要顯著提升系統的效率,就一定要提升整個系統絕大部分的執行速度
- $T_{old}和T_{new}$代表舊與新的時間
- $\alpha$是占總時間的比例
- $k$是提升多少係數
$T_{new} = (1 - \alpha)T_{old} + (\alpha T_{old}) / k = T_{old}[(1 - \alpha) + \alpha / k]$
可以得到加速$S = T_{old} / T_{new}$
Ex.
假設一個任務佔了某系統60%的時間($\alpha = 0.6$),加快了係數3($k = 3$),那套用公式就可以得到$S = 1 / {(1 - 0.6) + 0.6 / 3}$,$S = 1.67$,速度約提升1.67
倍
即便提升60%
系統部分的時間,加快的效果也不顯著,所以為了顯著提升,越大部分能提升越好,最好是整個系統都能提升係數k
。上述例子如果改成整個系統提升k
係數的話那可以直接提升5
倍速度
Concurrency and Parallelism (並行性與平行性)
Concurrency
: 一個系統有多個同時執行的活動Parallelism
: 用Concurrency
來讓系統執行更快
Parallelism
可以用不同程度的abstraction
來包裝
Thread-Level Concurrency
uniprocessor system
通過快速切換任務來達成concurrency
multiprocessor system
同時使用多個處理器來並行,通常使用multi-core processors
和hyperthreading
- 多核之間共享高階
cache
以及main memory
Hyperthreading
(超執行緒)有時稱作simultaneous multi-threading
,允許單個CPU
執行多個控制流,簡單來說就是一個核心多個thread
,例如Intel Core i7
每個核心都有兩個threads
,所以四核心就會有八個threads
Instruction-Level Parallelism
- 在更底層的
abstraction
- 現代處理器可以一次執行多個命令,一個
clock cycle
可以執行2~4
個指令,雖然需要比較多的cycles
(20),但可以執行更多命令;以前(1978)一次只能執行一條命令,且需要多個clock cycles
(3~10) pipelining
把指令切開成多個步驟,以達到並行的目的superscalar
(超純量)CPU架構可以讓一顆核心執行指令級並行的運算
Single-Instruction, Multiple-Data (SIMD) Parallelism
- 在最底層,現代處理器有特殊的硬體讓單一個指令可以變成多個動作,藉此並行運算,這種模式就稱為
SIMD parallelism
Abstraction是CS最重要的概念之一
- 有了
abstraction
可以讓程式統一,進而讓不同硬體也能執行同一個程式,也能讓程式設計師在寫程式的時候不用把每個層級實際要做的事情都寫下來