1.2 磁碟的物理結構與存取成本#
概念說明#
傳統硬碟(HDD)是機械裝置。一顆硬碟內有多片碟盤(platter),每片碟盤由磁頭(head)讀寫,碟盤分為許多磁軌(track),每條磁軌再切成多個磁區(sector,實體最小單位,通常 512B),作業系統再把多個磁區合成區塊(block,邏輯最小單位,常為 4KB)。所有碟盤同一半徑上的磁軌疊成一個磁柱(cylinder)。
完成一次磁碟 I/O,時間由四部分組成:
- 尋找時間(seek time):磁頭移動到目標磁軌的時間。全行程約 8~12ms,平均約 4~8ms。
- 旋轉延遲(rotational latency):等待葉片轉到目標磁區。7200 RPM 轉一圈 8.33ms,平均旋轉延遲約 4.17ms。
- 傳輸時間(transfer time):實際讀寫資料。循序讀取約 100~250MB/s。
- 控制器與佇列時間:定址、排隊等。
典型 7200 RPM 硬碟,一次隨機存取約 5~12ms。這個毫秒級常數乘上千萬次 I/O,就是所有磁碟演算法的成本核心。關鍵洞察:
- 隨機 I/O 天價,循序 I/O 便宜:讀連續磁區幾乎不需尋找與旋轉時間,吞吐量高達 200MB/s;隨機 4KB 讀取卻只有 100~200 IOPS。
- 一次多帶一點:若一頁 4KB 已花 10ms,順便多讀 100KB 只要 0.5ms,攤提成本極低。這催生了預讀(readahead)、多頁節點、合併排序等所有磁碟演算法。
演算法步驟:推導成本模型與模擬#
步驟 1:寫下單次 I/O 時間 T = seek_avg + rotational_avg + B/transfer_rate
步驟 2:演算法總時間 = T × I/O 次數,把「I/O 次數」當作與計數比較同等重要的一階複雜度。
步驟 3:比較不同佈局策略的 I/O 次數(連續 vs 隨機)。
# 01.2 範例:HDD 成本模型模擬
import random, math
DISK = {
"rpm": 7200,
"seek_avg_ms": 6.0,
"transfer_rate_MBps": 150, # 循序傳輸
}
def rotation_avg_ms(rpm: int) -> float:
return 60_000 / rpm / 2 # 平均半圈
def io_time_ms(block_kb: float, seek_ms: float = DISK["seek_avg_ms"]) -> float:
rot = rotation_avg_ms(DISK["rpm"])
transfer = block_kb / 1024 / DISK["transfer_rate_MBps"] * 1000
return seek_ms + rot + transfer
def read_blocks_random(n, block_kb=4):
"""隨機讀 n 個 4KB 區塊"""
per = io_time_ms(block_kb)
return n * per
def read_blocks_sequential(n, block_kb=4):
"""循序讀相鄰區塊:只有第一次要尋找"""
total_kbytes = n * block_kb
return DISK["seek_avg_ms"] + rotation_avg_ms(DISK["rpm"]) + \
total_kbytes / 1024 / DISK["transfer_rate_MBps"] * 1000
# 展示:量級比較
n = 100_000 # 100 萬個... 這裡示 10 萬個 4KB 區塊(約 400MB)
print(f"平均單次 4KB 隨機 I/O : {io_time_ms(4):.2f} ms")
print(f"10 萬區塊隨機讀取 : {read_blocks_random(n)/1000:.1f} 秒")
print(f"10 萬區塊循序讀取 : {read_blocks_sequential(n)/1000:.1f} 秒")
print(f"循序/隨機加速倍數 : {read_blocks_random(n)/read_blocks_sequential(n):.2f}x")
# QPS / IOPS 推導
print(f"HDD 隨機 4KB IOPS 理論上限: {1000 / io_time_ms(4):.0f} 次/秒")
執行後可見:同樣 400MB 資料,隨機讀約需 20 分鐘(10ms × 10 萬),循序讀只要約 3 秒,相差數百倍。任何排序、索引演算法都應該依此取捨。
複雜度與 I/O 成本分析#
設平均尋找 6ms、旋轉延遲 4.17ms、傳輸 150MB/s:
| 存取模式 | 單次時間 | 有效 IOPS | 有效吞吐 |
|---|---|---|---|
| 隨機 4KB | 約 10.3ms | 約 97 | 約 0.39 MB/s |
| 循序連續 | 約 0.03ms/4KB | 幾乎無限 | 約 150 MB/s |
| 8KB 隨機 | 約 10.4ms | 約 96 | 約 0.77 MB/s |
結論:增加每顆 I/O 的資料量幾乎不增加時間,因此「大頁、批次、預讀」是磁碟演算法的三大基石。這也導致磁碟演算法習慣以「I/O 次數」為第一階複雜度,例如檔案系統檔案指標定址鏈的長度、B-tree 的高度等。
練習#
- 一顆 7200 RPM 硬碟,平均尋找 8ms,請推導一次隨機 4KB 讀取所需時間,以及每秒最多能服務多少隨機讀取。
- 同上硬碟,讀 100MB 的容量(一個 100MB 的檔案),若資料連續存放與隨機分散成 2 萬個 4KB 片段,各需多久?由此推論檔案碎片的效能影響。
- 寫程式改模擬以上成本模型,改變
block_kb從 1 到 64 KB,畫出「吞吐量 vs 區塊大小」曲線,並說明為何越大區塊但傳輸時間不是問題。