JOVANA
Explore Library Glossary Getting Started Three Levels Fields How it works Mission
Join the mission
All guides

硬碟與固態硬碟之爭

儲存裝置是斷電之後資料安身的地方——而它是怎麼造出來的,決定了你的程式是飛還是爬。來認識會旋轉、帶著尋軌與旋轉等待的硬碟,以及沒有任何活動零件、卻有個古怪規矩的快閃固態硬碟:你可以寫一個頁,但你必須先抹掉一整個區塊。兩種技術,同一份工作,性格卻天差地別。

為什麼儲存是自成一格的怪獸

在本級稍早,我們用三個數字去衡量 I/O——吞吐量延遲與可靠性——也學會了如何用輪詢中斷DMA 跨過一條匯流排或一條 PCIe 連結來搬資料。儲存裝置就是這一整套機器最遠那一端的裝置,而它有一個記憶體沒有的性質:它是非揮發性(non-volatile)的。拔掉插頭,DRAM 在幾毫秒內就忘光一切,但一顆非揮發性的硬碟或 SSD 卻能替你保存檔案好幾年。那份持久正是儲存之所以存在的全部理由——而它是用速度上昂貴的代價買來的。

有多昂貴?回想記憶體階層:暫存器命中是不到一奈秒,L1 快取命中是兩三奈秒,一次 DRAM 存取大約一百奈秒。儲存裝置住在再往下好幾級的地方。一顆現代的快閃 SSD 在數十微秒內回應——比 DRAM 慢一千倍——而一顆會旋轉的硬碟則要毫秒級,又再慢上一百倍。講白了:如果 L1 命中是一次心跳,那麼一次硬碟尋軌就是一場長達一個月的遠征。正是這道鴻溝,讓作業系統如此賣力地把你的工作資料留在上層的 RAM 與快取裡,盡可能少去碰儲存裝置。

硬碟:一台追著資料跑的唱片機

一顆硬碟(hard disk drive)幾乎可以照字面說,就是一台微型的高精度唱片機。一片或多片堅硬的碟片每分鐘旋轉數千轉,表面鍍著一層磁性薄膜,一個裝在擺動臂末端的磁頭,靠著感應或翻轉一塊塊小區域的磁化方向來讀寫。資料被排成一圈圈同心的環,叫做磁軌(track),每條磁軌再切成一個個磁區(sector,是你能讀或寫的最小單位,歷史上是 512 位元組,如今通常是 4096)。要讀一個磁區,磁頭必須抵達對的磁軌,而對的那塊小區域必須轉到它底下。這兩件事都是機械運動——而機械運動跟電子學比起來,慢得像冰河。

所以取一個磁區的時間,誠實地拆成三部分。第一,尋軌時間(seek time):把臂擺到目標磁軌,平均幾毫秒,因為它取決於磁頭得跑多遠。第二,旋轉延遲(rotational latency):等旋轉的碟片把想要的磁區轉到磁頭底下——平均要半圈,在 7200 轉/分下大約是 4 毫秒。第三,傳輸時間(transfer time):定位好之後真正把位元組串流出來,這很快。把尋軌與旋轉等待加起來,一次隨機讀取在第一個有用的位元組到來之前,大約要花 8 到 10 毫秒。在那段時間裡,處理器本可以跑上數千萬條指令——這正是為什麼一個受硬碟所限的程式會卡住。

  Cost of one random 4 KB read from a 7200-rpm hard disk:

    seek time          ~ 4.0 ms   (swing the arm to the track)
    rotational latency ~ 4.2 ms   (half a turn at 7200 rpm)
    transfer time      ~ 0.03 ms  (stream 4 KB once positioned)
    -----------------------------
    total              ~ 8.2 ms   <-- before one useful byte

  Sequential read (next sector already under the head):
    no seek, no rotational wait  ->  ~0.03 ms per 4 KB

  Random is ~250x slower than sequential. Layout is everything.
硬碟上的機械稅。一次隨機讀取要付尋軌加旋轉延遲;一次循序讀取兩者都不付,所以對完全一樣的位元組,它可以快上數百倍。

那段追蹤裡藏著的教訓是:在硬碟上,資料坐在哪裡關係重大。讀一塊排成一段連續區間的百萬位元組很快——尋軌一次,之後磁頭只要讓碟片一個磁區接一個磁區地餵進它底下就好。讀同樣一塊、卻散落在碟面各處的百萬位元組,每一個片段都要重付一次尋軌與旋轉,對一模一樣的資料可以慢上數百倍。這跟快取是同一個區域性的故事,只是低了一層:循序存取是朋友,隨機存取是敵人,而一個破碎的檔案、或一個做著零散查找的資料庫,可以慢得爬,而原始傳輸率卻閒置在那兒。

固態硬碟:沒有活動零件的快閃——但有個圈套

一顆固態硬碟(solid-state drive)把碟片與臂整個扔掉。它把位元存進快閃記憶體(flash memory):一格格用捕捉電荷來在沒有電力下記住一個值的單元,沒有任何要等的機械零件。光是這一點就消滅了尋軌時間與旋轉延遲,所以 SSD 在數十微秒內就回應一次隨機讀取——比硬碟快上兩三百倍——而且關鍵在於,隨機存取幾乎跟循序一樣快,因為沒有磁頭要擺。這就是為什麼把硬碟換成 SSD 會讓一台老機器像重生:每一個過去要付尋軌成本的零散小讀取,如今幾乎是免費的。

但快閃藏著一個古怪的不對稱,它形塑了 SSD 行為的一切。你可以隨意任何一個(page,一塊比如 4 或 16 KB)。你只能在一個頁是空白時才它。而圈套在這裡:你無法把單獨一個頁清空。快閃一次只能抹掉一整個區塊(block)——一個區塊是數十或數百個頁,也許幾百萬位元組。所以規矩是先抹後寫(erase-before-write),而抹除是以粗顆粒度進行的。要覆寫一個滿區塊中間的某一個 4 KB 頁,一顆天真的硬碟得把整個區塊抄出來、抹掉、再寫回去——一個極其昂貴的操作,叫做寫入放大。快閃還會磨損:每個區塊只能撐過有限次數的抹除循環,之後就無法可靠地留住電荷了。

快閃轉換層:一個安靜的幻術師

如果先抹後寫與磨損被赤裸裸地暴露出來,每個程式都得懂快閃,SSD 用起來會是場噩夢。取而代之的是,每顆 SSD 內部都藏著一台小小的嵌入式電腦,跑著快閃轉換層(flash translation layer,FTL)——一段韌體,對外呈現出跟硬碟一樣簡單的「讀或寫任意磁區」介面,底下卻在做遠為聰明的事。它的核心把戲是間接(indirection):它維護一張對映表,從作業系統使用的邏輯磁區編號,映到資料實際坐落的實體快閃頁,而且它隨時可以自由地搬動資料、改寫那張表。

間接一舉解決了兩個問題。要「覆寫」一個邏輯磁區,FTL 抹掉舊區塊;它只是把新資料寫到別處一個全新的、已經空白的頁,再更新對映表指向那裡,把舊頁標記為過期。這正是你在虛擬記憶體裡見過的位址轉換想法——一張從乾淨的邏輯視圖映到雜亂的實體現實的表,讓底下的機器自由重排。稍後,一個背景的垃圾收集器會回收滿是過期頁的區塊:它把任何仍然有效的頁抄到別處,再把整個區塊抹掉,讓它重新可寫。

  1. 作業系統說「把 4 KB 寫到邏輯磁區 5000」,期待著像硬碟那樣一次簡單的就地更新。
  2. FTL 不管磁區 5000 過去住在哪裡;它抓下一個空閒的、已經抹除好的頁,把新資料寫進去,再把舊的實體頁標記為過期。
  3. 它更新邏輯到實體的對映表,讓磁區 5000 現在指向新頁。作業系統看到的是一次即時覆寫;關鍵路徑上沒有任何區塊被抹除。
  4. 損耗平衡(wear leveling)把寫入分散到所有區塊,讓沒有任何單一區塊被抹除得遠多於其他區塊;垃圾收集稍後再把大致過期的區塊抹掉,補回空閒池。

那第四步點出了最後一塊拼圖:損耗平衡(wear leveling)。因為每個區塊只能忍受有限次數的抹除,FTL 刻意把寫入撒開,讓磨損被平均分擔——如果它總是重用同樣那少數幾個區塊,那些區塊會先死,而其餘的還嶄新,整顆硬碟就會早夭。把抹除攤開,意味著整顆硬碟作為一個整體優雅地老去。這一切聰明的誠實代價是,SSD 的行為難以預測:一次寫入可能瞬間完成,也可能卡在垃圾收集後面;而且因為控制器在暗中搬遷與重排資料,SSD 會隨著被填滿而磨損、變慢,那是一顆單純的硬碟從來沒有的方式。

如何選擇,以及那個誠實的瓶頸

那麼誰贏?兩者都不是徹底贏——它們在取捨。SSD 在延遲與隨機存取上輾壓硬碟,耗電更少、不發出噪音,還禁得起摔。硬碟仍然在每位元組的成本上、以及目前在最大的容量上勝出,這就是為什麼封存、備份、與倉儲級的「冷」儲存,至今仍以百萬計地轉動著碟片。一種常見的設計兩者並用:一顆快的 SSD 當快取、或放熱門的、對延遲敏感的資料,後面接便宜的硬碟裝大宗。對的答案由工作負載驅動——量一量你的存取模式,別假設最新的技術自動在每個面向上都贏。

不論你選哪一個,都要抓住本級開頭那條直白的真相:對很多很多真實的程式來說,I/O 才是真正的瓶頸,而不是 CPU。你可以買一顆快兩倍的處理器,卻幾乎看不到改善——如果你的程式一輩子都在等儲存裝置的話。那不過又是一次 Amdahl 定律,你沒有加速的那部分主宰了一切。資料庫、網頁伺服器、檔案處理與資料分析,常態地受 I/O 所限,這正是為什麼有那麼多功夫花在把資料快取進 RAM、把請求成批處理、用 DMA 讓 I/O 與計算重疊、以及把資料擺成適合循序存取的樣子。