藏在一次讀取裡的三段等待
從上一篇你已經知道硬碟的樣子:剛性碟片裝在主軸上旋轉,一顆讀寫頭乘著一支會擺進擺出的臂,而你的資料鋪在一圈圈被分成磁區的同心磁軌上。現在我們要問那個主宰作業系統幾乎每一個儲存決策的問題:當硬碟被指示去讀某一個磁區時,它到底要花多久?誠實的答案是沒有單一的數字——一次磁碟存取是三段接連疊起來的獨立延遲,而它們的大小天差地遠。
從實體上想像。讀寫頭正停在某條磁軌上,而你要的磁區在另一條磁軌上。首先,臂得移動到正確的磁軌——這就是搜尋時間。一旦讀寫頭停在正確的磁軌上,你想要的磁區大概還在那一圈的別處,所以你得等碟片把它轉到讀寫頭下方——這就是旋轉延遲。直到此刻,讀寫頭既在對的磁軌上、對的磁區又正轉過來,位元才真正能成串經過並被讀出——這就是傳輸時間。總存取時間就是這三者相加:搜尋 + 旋轉 + 傳輸。
ask for one sector --> | seek | rotation | transfer | --> bytes in hand
~9 ms ~4 ms ~0.05 ms
(move arm)(wait spin)(read bits)
total access time = seek_time + rotational_latency + transfer_time搜尋與旋轉:昂貴的那兩段
搜尋時間是臂的旅程。想像一位只能筆直走進某一條走道的圖書管理員:在她讀到任何一個書名之前,得先實際走到正確的走道,而跳到隔壁走道幾乎不花錢,但行軍到館的另一頭就要花上真正的時間。搜尋到相鄰磁軌可能遠不到一毫秒;橫掃整片碟片則要好幾毫秒。因為臂可能停在任何地方,硬碟廠商會標一個平均搜尋時間——對典型的桌上型硬碟而言大約 8 到 12 毫秒——意思是你越過一段隨機距離所要付的搜尋。
旋轉延遲是純粹地等待旋轉。讀寫頭現在已在對的磁軌上,但你要的磁區得轉過來——就像一個轉盤,你只能在某道菜經過你正前方時去拿它。如果那道菜剛溜過去,你要等將近一整圈;如果它正要到,幾乎不必等;平均而言是半圈。而這個平均值直接從轉速算得出來。在每分鐘 7200 轉下,碟片每 60 / 7200 秒轉一圈,約 8.33 毫秒,所以平均旋轉延遲是它的一半,約 4.17 毫秒。這段延遲是一道硬底線:在資料轉來之前,硬碟除了旋轉什麼也做不了,而軟體再聰明也抹不掉它。
傳輸:便宜的那一段(以及為何大小幾乎無關緊要)
傳輸時間是你真正在讀位元的唯一一段,而它和另外兩段相比小得驚人。一旦讀寫頭已在對的磁軌上、對的磁區又正轉來,碟片本就在轉,資料便以表面移動的速度成串掠過讀寫頭。一顆現代硬碟的傳輸速率約為每秒 100 到 200 MB,所以一個 4 KB 的區塊只要數十微秒——也許 0.02 到 0.05 毫秒。把它擺到 9 毫秒的搜尋與 4 毫秒的旋轉旁邊,教訓就直白了:對單一一次小讀取,你花在「就定位」的時間超過九成九,真正花在讀的幾乎沒有。
這一個事實有著巨大的後果:一旦你付了搜尋與旋轉,讀更多幾乎是免費的。讀一個磁區,和讀同一磁軌上接下來的十六個磁區,代價幾乎一樣,因為沉重的費用(就定位)是一次性的,成長的只有那微小的傳輸。這正是為什麼在硬碟上循序存取輾壓隨機存取。讀取一塊放在連續一段裡的 MB,也許只要一次搜尋加一次旋轉加上不多的傳輸;讀取同樣一塊、卻被打散成 256 小片散落整片碟片的 MB,則可能要 256 次搜尋與 256 次旋轉——對相同的位元組數,輕易就慢上百倍。
一個小小的推演範例
我們替一顆 7200 RPM、平均搜尋 9 毫秒、傳輸速率每秒 200 MB 的硬碟,替它的單一一次隨機讀取標上真實的數字。看看總和如何組裝起來,又如何一面倒。
- 搜尋:臂移動到目標磁軌。平均搜尋 = 約 9 毫秒。(至此成本:9 毫秒。)
- 旋轉:等磁區轉到讀寫頭下方。7200 RPM 下一圈 = 60 / 7200 秒 = 8.33 毫秒,所以平均延遲 = 半圈 = 約 4.17 毫秒。(至此成本:13.17 毫秒。)
- 傳輸:以每秒 200 MB 讀一個 4 KB 區塊。時間 = 4 KB ÷ 200 MB/s = 約 0.02 毫秒。(總計:約 13.19 毫秒。)
- 從數字裡讀出教訓:13.19 毫秒中,搜尋與旋轉占了 13.17 毫秒——約 99.8%——而真正的讀取只是個四捨五入的零頭。
現在比較讀取 64 KB 的兩種方式。作為同一磁軌上連續的 16 個 4 KB 區塊:付一次搜尋(9 毫秒)+ 一次旋轉(4.17 毫秒)+ 64 KB 的傳輸(約 0.3 毫秒)= 約 13.5 毫秒。作為隨機散落的 16 個區塊:付 16 次各約 13.19 毫秒的完整存取 = 約 211 毫秒。同樣 64 KB、同一顆硬碟,純粹因為區塊不連續就慢了約 16 倍。也把 13.19 毫秒放進脈絡裡看:在這單單一次隨機讀取裡,一顆 3 GHz 的 CPU 早已執行了數千萬條指令。這道鴻溝正是硬碟坐在儲存階層最慢底端的原因,也是作業系統如此賣力避免去碰它的原因。
扁平的編號,以及代價的去向
有一個值得誠實點名的微妙之處。作業系統其實已不再用磁柱、磁頭、磁區來說話了。它透過邏輯區塊定址(LBA)以單一一個扁平的號碼來要求區塊——「從 LBA 50000 開始讀 8 個區塊」——再由硬碟自己的控制器把那個號碼映射到目前真正存著它的實體幾何上。所以軟體根本無法直接看見搜尋與旋轉;它只看見回傳的總時間,必須去推理底下那套隱藏的機械運作。
作業系統倚靠一個務實的假設來橋接那道鴻溝:編號相近的 LBA 在實體上大概也相近,所以連號的 LBA 通常能在很少、甚至沒有額外搜尋的情況下讀出。正是這點讓「把一個檔案的區塊放在連號的 LBA 上」得以轉譯成「能快速讀它」。不過要對它的限制誠實:它是一個提示,而非保證。因為控制器可能悄悄把一個磨損的磁區重新映射到別處,兩個連號的 LBA 不一定在實體上相鄰,而一個作業系統以為整齊連續的檔案,仍可能付出一次意外的搜尋。這個模型對的時候遠多於錯的時候,這正是它有用的原因——但它是一個模型,不是真相。
而這裡有個這個階段其餘部分都倚賴的轉折:上面每一個字講的都是會旋轉的硬碟。一顆固態硬碟沒有臂、也沒有碟片,所以它根本沒有搜尋時間、也沒有旋轉延遲——對快閃晶片定址是電子式的,就像 RAM 那樣。這就溶解了我們整篇花力氣談的那份失衡:在快閃記憶體上,隨機存取幾乎和循序存取一樣快,所以那些為了把臂移動降到最低而建的精巧排程把戲(下一篇的主題)幾乎買不到任何好處。請把這份代價拆解牢牢記住,因為接下來幾篇基本上就是圍繞著它而建的作業系統設計的故事——以及固態硬碟如何悄悄抽掉了其中好幾個設計的腳。