HDDのプラッタ外周部と内周部の性能差を検証してみた

けっこう前に秋葉原に行ったときに何も買わずに帰るのも失礼かと思い,カッとなって値崩れしたHDDとSSDを買って帰った.自宅サーバでいろいろ遊んでみる予定だったのだけれど,そのまま放置して3ヶ月近く経ってしまった.最近になってタイの洪水の影響でHDDが高騰しているのでびっくりしている.(2TB HDDが2万円超え・・・だと・・・)

さて,この実験をしたくなった原因はだーいーぶ前のWSDM2009のJeff Dean講演のときにチラっと出てきたインデクスの外周部と内周部の使い分け.当たり前のようにさらっと書かれていた.

HDDの外周部はパフォーマンス高いんで,インデクスはこっちに置くよ.

という部分.へー,そんなに性能が違うのか今度HDD買ったら試してみようと思ってから早2年.ようやく思い立って検証した.

なお,Jeff Deanの講演資料は以下にアップされている.

@llamerada さんの素晴らしい翻訳はこちら

HDDの基本知識

今回の実験をするにあたり,HDDまわりの勉強をしたのでまとめてみる.自分の頭の整理のために書いているので,HDDの構造については僕が語るまでもないので他の資料を参考にしてほしい.今回の件でいろいろ漁ったけれど,以下の資料が特に秀逸で,おすすめ.

HDDは複数のプラッタから成っており,ひとつのプラッタの同心円状にデータを記憶している.同心円状の領域をトラックと呼び,トラックの中には複数のセクタが配置されている.

図を描くのは面倒なので,こんな感じ.

というように○(トラック)が何重にも配置されている.トラックの中にはセクタと呼ばれる領域が連続して存在する.そんでもって,プラッタは複数重なっているので,全てのプラッタの同一トラックをまとめてシリンダと呼ぶ.fdiskを使ったことがある人はわかるけれど,HDDのパーティションはこのシリンダ単位で指定する.

ここらへんは図をみると一発なので,上述の資料などを見るとよいと思う.

さてさて,セクタの大きさが同じだと考えると,当然「内側はトラックあたりのセクタ数が少なく,外側はトラックあたりのセクタ数が多くなる」と思う.さて,これを考えたときに「セクタの大きさが一定なのか? トラックあたりのセクタ数が一定なのか?」という疑問が湧いた.

どうやら,今昔で異なるようで

  • 昔はセクタのトラックあたりのセクタ数が一定 (角速度一定方式)
  • 今はセクタの大きさが一定 (記録密度一定方式)

というのが答えらしい.これも上述の資料に詳しく書かかれている.というわけで現代のHDDでは,

プラッタ外周の方がトラックあたりのセクタ数は多い.データ密度が高い.

ということがわかった.(昔の角速度一定方式では,むしろ内周の方がデータ密度が高かった,というのは興味深い)

セクタ,トラック,シリンダ,という概念を理解したところで,HDDのプラッタ上の位置の指定方法についてちょっとだけ余談.

どうやら現在ではLBA (Logical Block Addressing) という全セクタに通し番号を振る方式がとられている.IDE規格ではCHS (Cylinder/Head/Sector) 方式,ATA以降LBA方式を取っているとのこと.LBA方式のメリットとしてはスペアセクタの利用が可能になるなどが挙げられる.ただし,以下の資料によると,OSから指定されたLBAはファームウェアによって内部でCHSに変換されているという.

つづいてHDDに対してデータを読み書きは,ざっくり言うと

  • 1. 読み取りヘッダが目的のセクタが存在するトラックへ移動 (シークタイム; Seek time)
  • 2. 目的のセクタがヘッダ位置に来るまで回転を待つ (回転待ち時間; Rotational latency)

という流れで行う.(余談だけれど,読み取りヘッダは普段軽くプラッタに接していて,プラッタが回転すると空気圧でプラッタからわずかに離れるらしい! すごい!)

HDDのレイテンシはこの「シークに発生する時間+プラッタの回転待ち」の合計値となる.ディスクは常に回転しているので,たとえデータが連続して隣のトラックに書き込まれていても,ヘッダを隣のトラックに移動してから,続きのセクタを読み取るまでプラッタが回転するのを待たなければいけない.平均するとプラッタが半回転する時間を待たなければいけないので,これはとても嫌だ.

参考までにブッチャー本[1]の付録(p.593)には,以下の数字が書かれている.

  • Average rotational latency: 4.2 msec (7000rpm)
  • Average seek latency: 8.6 msec
  • Average random access latency: 12.8 msec

これは遅い!

というわけで,誘導尋問的に

同一トラックにデータがより多く詰まっていた方がシークも発生しづらいから嬉しいんじゃね?

という気持ちになる.

さらに詳しいことを言うとHDDにはパフォーマンス向上のためにキャッシュ用メモリ (先読みバッファ) が積まれている.HDDはヘッダがディスクに要求された目的のセクタに到達するまでにひたすら同一トラックの各セクタを先読みバッファに記録してくれる.これからも「データは同一トラックにたくさん詰まっていた方がいいなぁ」という気分はさらに強まる.

以上より,HDDの構造と仕組みを考えると「外周部の方が内周部に比べてパフォーマンスが高いだろう」という気持ちになったところで実験を開始する.


実験

実験条件その他もろもろの情報は以下のとおり.

  • 実験条件
  • 実験方法
    • bonnie++の実行結果を取得
      • 設定はデフォルト値を利用 (24GBメモリなので-s 48GB相当)

実験ではWestern Degitalのキャビアグリーン2TBハードディスクを使用した.実験した後から知ったのだけれど, こいつは6.0Gbps SATA接続が可能らしい.まぁそこまでパフォーマンス出なかったのと,内周・外周の比較なので気にしない.

シリンダ番号が若い方が外周らしいので,fdiskで小さい方から500GBずつ切った.なお,なぜかfdiskだとprimary partitionを4つ作れなかったのでcfdiskを使った

# LC_ALL=C LANG=C cfdisk /dev/sdb

ファイルシステムによってもパフォーマンスは変わるのだろうけれど,以下のファイルシステム毎のベンチマーク結果を見て,まぁそんなに悪くないだろう,というのと,内周vs.外周の比較なのであまり気にせずext4を選択した.tune2fsを用いたチューニングは一切しなかった.

(上記著者のベンチマーク補記事項に感動.これに比べて僕の適当な実験といったら・・・)

bonnie++は,デフォルト設定だと高速なデバイスに対して時間が計測できないらしいのでコンパイルする前に以下のようにヘッダファイルを変更.

% tar -zvxf bonnie++-1.03e.tgz && cd bonnie++-1.03e
% vi bonnie.h
% diff bonnie.h.old bonnie.h
17c17
< #define MinTime (0.5)
---
> #define MinTime (0.01)

% ./configure && make
  • 実験結果
# 最外周
% cat hdd1_bonnie++.out

Version 1.03e       ------Sequential Output------ --Sequential Input- --Random-
                    -Per Chr- --Block-- -Rewrite- -Per Chr- --Block-- --Seeks--
Machine        Size K/sec %CP K/sec %CP K/sec %CP K/sec %CP K/sec %CP  /sec %CP
myhost       48312M 114205  96 118003   9 31951   3 83020  81 145385   5 210.6   0
                    ------Sequential Create------ --------Random Create--------
                    -Create-- --Read--- -Delete-- -Create-- --Read--- -Delete--
              files  /sec %CP  /sec %CP  /sec %CP  /sec %CP  /sec %CP  /sec %CP
                 16 97039  97 853774  83 152938 100 104650  99 1324712  97 152024 100
myhost,48312M,114205,96,118003,9,31951,3,83020,81,145385,5,210.6,0,16,97039,97,853774,83,152938,100,104650,99,1324712,97,152024,100


# 外から2番目
% cat hdd2_bonnie++.out

Version 1.03e       ------Sequential Output------ --Sequential Input- --Random-
                    -Per Chr- --Block-- -Rewrite- -Per Chr- --Block-- --Seeks--
Machine        Size K/sec %CP K/sec %CP K/sec %CP K/sec %CP K/sec %CP  /sec %CP
myhost       48312M 106508  91 106448   8 30597   3 82684  82 133009   5 202.6   0
                    ------Sequential Create------ --------Random Create--------
                    -Create-- --Read--- -Delete-- -Create-- --Read--- -Delete--
              files  /sec %CP  /sec %CP  /sec %CP  /sec %CP  /sec %CP  /sec %CP
                 16 96085  96 879204 107 152988 100 104987  99 1364998  99 153386 101
myhost,48312M,106508,91,106448,8,30597,3,82684,82,133009,5,202.6,0,16,96085,96,879204,107,152988,100,104987,99,1364998,99,153386,101


# 外から3番目
% cat hdd3_bonnie++.out

Version 1.03e       ------Sequential Output------ --Sequential Input- --Random-
                    -Per Chr- --Block-- -Rewrite- -Per Chr- --Block-- --Seeks--
Machine        Size K/sec %CP K/sec %CP K/sec %CP K/sec %CP K/sec %CP  /sec %CP
myhost       48312M 95743  84 97315   8 37906   3 81638  81 117383   4 213.4   0
                    ------Sequential Create------ --------Random Create--------
                    -Create-- --Read--- -Delete-- -Create-- --Read--- -Delete--
              files  /sec %CP  /sec %CP  /sec %CP  /sec %CP  /sec %CP  /sec %CP
                 16 96656  96 878540 107 152490 100 105254 100 1363508  99 151412  99
myhost,48312M,95743,84,97315,8,37906,3,81638,81,117383,4,213.4,0,16,96656,96,878540,107,152490,100,105254,100,1363508,99,151412,99


# 最内周
% cat hdd4_bonnie++.out

Version 1.03e       ------Sequential Output------ --Sequential Input- --Random-
                    -Per Chr- --Block-- -Rewrite- -Per Chr- --Block-- --Seeks--
Machine        Size K/sec %CP K/sec %CP K/sec %CP K/sec %CP K/sec %CP  /sec %CP
myhost          48312M 77108  69 76150   6 24960   2 71390  78 96856   3 206.0   0
                    ------Sequential Create------ --------Random Create--------
                    -Create-- --Read--- -Delete-- -Create-- --Read--- -Delete--
              files  /sec %CP  /sec %CP  /sec %CP  /sec %CP  /sec %CP  /sec %CP
                 16 102887  97 872129 106 152455 100 104920  99 1364320  99 152561 100
myhost,48312M,77108,69,76150,6,24960,2,71390,78,96856,3,206.0,0,16,102887,97,872129,106,152455,100,104920,99,1364320,99,152561,100

bonnie++の使い方や結果の見方についてはこちらを参照

一番気になるのは,シーケンシャルリードのパフォーマンスなので,Sequential InputのBlock単位の結果を並べてみる.

  • hdd1: 141.98 MB/sec
  • hdd2: 129.89 MB/sec
  • hdd3: 114.63 MB/sec
  • hdd4: 94.59 MB/sec

これは相当違う!! なんと最外周は最内周の1.5倍ものパフォーマンスをたたき出した.想像以上だったのでびっくり.グラフにするとこんな感じ.相当違う印象.

他にはこのベンチマークから以下の結果を確認した.

  • 1バイト単位の読み込み (Sequential Input: Per Chr) だと差は小さくなるものの,性能は外周>内周という関係は変わらない.
  • シーケンシャル書き込み (Sequential Output) でも性能は外周>内周という関係がある.
  • ランダムアクセス (Random Seeks) はほとんど変わらない.

(おまけ) USB接続の場合

実は上の実験をする前にUSB2.0接続で検証して差が出なかったのでそれも載せておく.

  • USB2.0接続 (最大60MiBps程度?)
  • 最外周と最内周の結果だけ

他は大体同じ.ただし,色々実験条件が違うので上述の結果との絶対的な差異に意味はないので注意.

# 最外周
Version 1.03e       ------Sequential Output------ --Sequential Input- --Random-
                    -Per Chr- --Block-- -Rewrite- -Per Chr- --Block-- --Seeks--
Machine        Size K/sec %CP K/sec %CP K/sec %CP K/sec %CP K/sec %CP  /sec %CP
myhost        6576M 19744  32 20599   4 12843   2 32609  46 33386   1 129.7   0
                    ------Sequential Create------ --------Random Create--------
                    -Create-- --Read--- -Delete-- -Create-- --Read--- -Delete--
              files  /sec %CP  /sec %CP  /sec %CP  /sec %CP  /sec %CP  /sec %CP
                 16 87175  89 592037 101 127278  99 98008 100 976754  95 130551  98
myhost,6576M,19744,32,20599,4,12843,2,32609,46,33386,1,129.7,0,16,87175,89,592037,101,127278,99,98008,100,976754,95,130551,98


# 最内周
Version 1.03e       ------Sequential Output------ --Sequential Input- --Random-
                    -Per Chr- --Block-- -Rewrite- -Per Chr- --Block-- --Seeks--
Machine        Size K/sec %CP K/sec %CP K/sec %CP K/sec %CP K/sec %CP  /sec %CP
myhost        6576M 18715  31 19707   4 12419   2 31989  76 33376   1 124.4   0
                    ------Sequential Create------ --------Random Create--------
                    -Create-- --Read--- -Delete-- -Create-- --Read--- -Delete--
              files  /sec %CP  /sec %CP  /sec %CP  /sec %CP  /sec %CP  /sec %CP
                 16 62296  65 620204  90 128369 100 97371  99 969470 118 131600  99
myhost,6576M,18715,31,19707,4,12419,2,31989,76,33376,1,124.4,0,16,62296,65,620204,90,128369,100,97371,99,969470,118,131600,99

USB接続だとそっちがI/Oボトルネックになってしまい,シーケンシャルリードに差が出ないことがわかった.


まとめ

だらだらと書いてしまったけれど,今回のまとめはこんなところ.

  • 読み込み/書き込みでは,外周>>内周
  • HDDはシリンダ番号が若い方が外周.
    • CDは逆らしい.データをちょこっとだけ書くとパフォーマンスが落ちるとか
  • 接続方式によってはそっちがボトルネックになるので注意 (USB2.0の場合)
  • fdiskはいろいろ適当

最近はメモリも安くなったし,SSD (最近だとFusion-IO?) がどんどん普及していくこのご時世に,HDDの効率利用はあまり直接的に役立つことは少ないのかもしれないけれど,HDFSのようにディスク上にデータを置く前提で考えると役に立つのかなぁ.とはいえ,GFS, HDFSもチャンク単位でデータを管理しているので,シーケンシャルリード性能の高さがどれだけ効くのかわからないが.

ちょっと計算すると,今回の実験設定でのfdisk情報を鵜呑みにすると1パーティション60788シリンダで500GBなので,シリンダあたりの容量は約8.42 MBとなる.HDFSではチャンクサイズが指定できるけれど,デフォルトで64MBとかなので,HadoopHDFSを構築するときにパーティションを切って外周部のみを利用することでパフォーマンス向上は見込めそうな気がする.容量的にはもったいないけれど.

HDDの構造や仕組みは何度か勉強した気がするけれど,まったく頭に残っていなかった.今回の件でかなり整理できた気がする.今回改めてウェブ検索してみると,どうやらHDD外周の方がパフォーマンスが高いというのは定説で,きちんとベンチマーク結果を取っている人がたくさんいた.僕は自分で体験しないと知識を吸収しない愚者なので,いろいろやってみた次第である.自分でやると忘れないし,付随した知識がいろいろ手に入るので楽しいなぁ.なかなか時間が取れないのだけれど...

以下の点がまだわかっていない.残った謎.

  • fdiskではシリンダ番号でパーティションを指定する.
  • 最近の記録密度一定方式では,シリンダあたりのセクタ数は異なるはずだけれど,どうやらシリンダ数とブロック数は比例しているように見える.内部で変換しているのだろうか?

さて実は同じ日に安くなっていたIntel 320とCrucial C300もカッとなって購入したので,そちらのベンチマークも計測してみた.記事が長くなってしまうのでそれは別の日に書く予定.こっちはSATA-III (最大6.0Gbps) 接続しているので驚く結果が出た,という無駄なヒキをつくってみる.

参考文献

  • [1] ブッチャー本

Information Retrieval: Implementing and Evaluating Search Engines (MIT Press)

Information Retrieval: Implementing and Evaluating Search Engines (MIT Press)