ディスクパフォーマンス計測ツールと戯れる [winsat/CrystalDiskMark/diskspd]

:::note info
Windowsで検証したのでタグはWindowsとしていますが、本記事で重点的に検証したdiskspd自体はLinux対応していて、Linuxでもオプション指定等の検証内容は有効だと思います。diskspdは高機能のため、今後機会があればLinuxでもdiskspdを利用したいです。
:::

オンプレWindowsサーバのディスク自体のパフォーマンスを計測したい＆継続的に色々な環境のデータを集めてディスクパフォーマンス周りのナレッジ(どのくらいのパフォーマンスの環境が多いのかとか、パフォーマンス問題が起きる環境のIOPSはこのぐらいが多いとか色々)を貯めたいなあ..と思い、パフォーマンス計測ツール/ベンチマークソフトを調べた。
ディスクのパフォーマンス測定というと真っ先にCrystalDiskMarkが浮かぶんだけど、フリーソフトは今回は却下。となるとどういう手法があるんだ?と調べてみると、ビルトインのwinsatというコマンドがあって、一応それで測れる(ただし、後から分かったけど、クライアントOSのみ対応で、サーバOSでは利用不可だった)。正式名称はWindows システム評価ツール(Windows System Assessment Tool)で、Windows エクスペリエンス(CPU性能評価とかするあれ)に関するやつみたい

# winsat

何も考えずに```winsat disk```と実行するだけで、シーケンシャルリード、シーケンシャルライト、ランダムリードをパッと測ってくれる。ランダムライトは測ってくれないので、オプションを付けて別途実行する```winsat disk -ran -write -n 0```。 ```-ran```はランダム、```-write```はライト、```-n 0```は1つめのディスクを指定している

実行結果はこんな感じ

```
C:\>winsat disk
Windows システム評価ツール
> 実行中: 機能の列挙 ''
> 実行時間 00:00:00.00
> 実行中: 記憶域の評価 '-seq -read -n 0'
> 実行時間 00:00:06.74
> 実行中: 記憶域の評価 '-ran -read -n 0'
> 実行時間 00:00:11.12
> 実行中: 記憶域の評価 '-scen 2009 -drive C:'
> 実行時間 00:01:09.03
> 実行中: 記憶域の評価 '-seq -write -drive C:'
> 実行時間 00:00:09.20
> 実行中: 記憶域の評価 '-flush -drive C: -seq'
> 実行時間 00:00:05.55
> 実行中: 記憶域の評価 '-flush -drive C: -ran'
> 実行時間 00:00:12.82
> 実行中: 記憶域の評価 '-hybrid -ran -read -n 0 -ransize 4096'
NV Cache not present.
> 実行時間 00:00:00.16
> 実行中: 記憶域の評価 '-hybrid -ran -read -n 0 -ransize 16384'
NV Cache not present.
> 実行時間 00:00:00.02
> Disk  Sequential 64.0 Read                   117.78 MB/s          6.9
> Disk  Random 16.0 Read                       1.45 MB/s          3.9
> Responsiveness: Average IO Rate              3.36 ms/IO          6.2
> Responsiveness: Grouped IOs                  10.28 units          7.1
> Responsiveness: Long IOs                     15.11 units          6.9
> Responsiveness: Overall                      155.25 units          6.6
> Responsiveness: PenaltyFactor                0.0
> Disk  Sequential 64.0 Write                  74.90 MB/s          6.1
> シーケンシャル書き込みでの平均読み取り時間                        5.162 ms          5.
> 待ち時間: 95 パーセンタイル                             40.187 ms          1.9
> 待ち時間: 最大                                     72.808 ms          7.8
> ランダム書き込みでの平均読み取り時間                           11.903 ms          4.0
> 合計実行時時間 00:01:55.43
```

```
C:\>winsat disk -ran -write -n 0
Windows システム評価ツール
> 実行中: 機能の列挙 ''
> 実行時間 00:00:00.00
> 実行中: 記憶域の評価 '-ran -write -n 0'
> 実行時間 00:00:01.68
> Disk  Random 16.0 Write                      13.15 MB/s
> 合計実行時時間 00:00:02.39
```

で、だらだらと出力されていてちょっと読みづらいが、要するにここを見る

```
> Disk  Sequential 64.0 Read                   118.46 MB/s          6.9
> Disk  Random 16.0 Read                       1.49 MB/s          3.9
> Disk  Sequential 64.0 Write                  74.19 MB/s          6.1
```

```
> Disk  Random 16.0 Write                      12.91 MB/s
```

ブロックサイズ64KBでのシーケンシャルリードは118.46MB/s、16KBでのランダムリードは1.49MB/sという感じ。
Windowsのアロケーションユニットサイズの標準は4KBなので、ブロックサイズでも4KBを指定し、ランダムアクセスのみ測る。その場合はこんな感じ

```
winsat disk -ran -ransize 4096 -n 0 -read
winsat disk -ran -ransize 4096 -n 0 -write
```

実行時間は短く(基本数秒。ただしディスクが遅いサーバでは数分かかることがある)、結果は安定していて、複数回測ってもほとんど同じ値なのでキャッシュうんぬんは気にせず1-2回測ればOKそうな感じ。
ただ、環境によって、ランダムライトがランダムリードの10倍くらい速いと記録されることがあって、嘘..?と(基本ライトの方が遅いと思うので)。

なお今回試したのは```winsat disk```だけだけど、[winsat](https://docs.microsoft.com/en-us/previous-versions/windows/it-pro/windows-server-2012-r2-and-2012/cc770542(v=ws.11))ツール自体はコンピュータの色々なリソースに対する包括的なパフォーマンス計測ツールのようで、```winsat cpu``` ```winsat mem```など色々測れる

で、パフォーマンスツールによって違う結果が出るんだろうな..というのは考慮としてあって、社内環境にCrystalDiskMark入れて測ってみる。やはり全然結果が違う..

# CrystalDiskMark

winsat実行と同じマシンでのCrystalDiskMark
ランダムアクセスのスレッド数をどちらも1とし、テスト回数を1回とした(回数を変えて繰り返し測ってもあまり変わらなかったため)。それ以外はデフォルト

スループット
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/620712/665e36c2-9728-99ef-c721-25087679e697.png)


IOPS
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/620712/09ae577a-1448-9ff2-d182-b2543cb41f92.png)

繰り返し実行したりタイミングを変えて実行するとたまに結果が違うので微妙なんだけど(共有環境なのもあり)、意外とキュー数などによって、ライトが速いケースもあるみたい。ただし、数値はwinsatよりだいぶ低い..

で、計測ツールについてさらに調べると、Microsoft謹製のdiskspdというパフォーマンス計測ツールがあり(しかもLinuxも対応)、実は[CrystalDiskMarkはdiskspdをラッパーしてる](https://crystalmark.info/ja/software/crystaldiskmark/)ということだった

> ベンチマーク機能
> CrystalDiskMark 4-8 は Microsoft DiskSpd (The MIT License) を使用しております。

Microsoft謹製かつ、コマンドラインツールなことが丁度良いので(結果をリダイレクトでテキストに落とせて楽)、diskspdを重点的に検証してみた

# diskspd

[GitHub](https://github.com/microsoft/diskspd)からzipをダウンロードして解凍、コマンドプロンプトでDiskSpdフォルダにcdして```diskspd.exe```を実行する(インストール不要 ※レジストリ汚れない)

diskspdは[オプション](https://github.com/Microsoft/diskspd/wiki/Command-line-and-parameters)が豊富で、リードとライトの割合や、キャッシュの有効/無効を指定できたりする。CrystalDiskMarkのラッパー元なので、CrystalDiskMarkにある設定項目も指定できる。出力結果も、スループット/IOPSに加え、レイテンシーのパーセンタイルや標準偏差など色々出せる。

試行錯誤の結果、実行時オプションは以下のようにしてる。

```
ランダムリード
diskspd.exe -b4K -c1G -r -o1 -t1 -Sh -n -D -L -W0 -d10 -w0 testfile.dat
diskspd.exe -b4K -c1G -r -o32 -t1 -Sh -n -D -L -W0 -d10 -w0 testfile.dat

ランダムライト
diskspd.exe -b4K -c1G -r -o1 -t1 -Sh -n -D -L -W0 -d10 -w100 testfile.dat
diskspd.exe -b4K -c1G -r -o32 -t1 -Sh -n -D -L -W0 -d10 -w100 testfile.dat
```

各オプションの説明はこんな感じ

```
-b4K ブロックサイズ4KB(Windows標準のアロケーションユニットサイズを指定)
-c1G テスト用ファイル(testfile.dat)のサイズ。ファイルは初回実行時に自動生成される。フォルダ名指定しない場合はカレントディレクトリに生成される。
-r ランダムリード/ライト指定(シーケンシャルリード/ライトにしたい場合は-s)。-r4Kとブロックサイズまで指定しても良いかもしれない。英語圏のサイトの実行例では-r指定が多かったのと、実行時出力上でも-b4Kと-rの組み合わせでusing random I/O (alignment: 4096)と出力されているので-rで良いと判断
-o1[-o32] キューデプス。CrystalDiskMarkのキューと同じ。ネイティブコマンドキューイング(NCQ)対応のディスクの場合シークタイムの効率化が実施されIOPSが高くなる。未対応(1)、NCQ最大値対応(32)の両方のケースの測定ができるように、このオプションを分けてリード/ライトを各2回実施している
-t1 同時スレッド数。今回はシリアルアクセスのワークロードを想定しているので1を指定(同時アクセスの想定でここの数値を変える)。ちなみにスレッド数が2以上の場合、各スレッドでの結果はかなり低く、スレッド合計ではかなり高くなる。スレッド数の指定と評価は結構難しい感じがする
-Sh ソフトウェア/ハードウェア的なキャッシュ両方を無効 (-hオプションはduplicate)
-n CPUアフィニティを無効(CrystalDiskMarkのデフォルトが無効だったので合わせた。ちなみに有効(diskspdデフォ)でも無効でも結果に影響はなかった。CPUの各コアの使い方まで細かく考慮する必要は今回は感じてない)
-D IopsStdDev(IOPSの標準偏差)が追加で出力される。いらないけど一応
-L レイテンシー統計を出力
-W0 実行時間のうち、最初の方の計測結果を無視したいときは秒数を指定。今回は検証後最終的に実行時間を10秒と長めに取っていて、最初の方の結果を捨てても捨てなくても結果に大きな違いはないので、0を指定。ただし計測時間が数秒と短いとか、他のオプション指定との組み合わせによっては、最初の1～数秒間の結果がかなり良くなるケースがある。そういう場合は最初の部分を捨てるといいと思う。
-d10 実行時間を指定。10秒はデフォルトと同じ。実際1秒～20秒の各秒数で繰り返し計測したが、10秒くらいが結果が安定してるなという感じだった。1秒でも安定してるときは安定してるんだけど念のため長めに。
-w0[-w100] ライトの割合。-w0を指定するとリードオンリー。-w100を指定するとライトオンリー。リード/ライト両方を想定したシナリオの場合、ここで割合を指定できる
testfile.dat テストファイル名。このファイルが-cで指定したサイズで作成される
```

ちなみに他にも有用なオプションはある。この辺

```
-Zr テスト用データ(テスト用ファイルに書き込むバイト文字列)をランダムな値にする。デフォルトはzero埋め。ランダムな値の方が良い感はある。繰り返し試してみたけど結果に影響しなかったので指定してない。-z/-Z系はテスト用データに関するオプション
-v verbose。詳細や進捗を出力する。進捗は、最初はあるとなんかいいかもしれない。慣れるといらない。追加の情報で特に必要そうなものはなかったので指定してない
```

なお、一番影響がでかい要素は、意外と```-c1G```部分のテスト用ファイルサイズ指定だった。CrystalDiskMarkではデフォルトで1GBとなっている。diskspdの計測結果は、実行時間ベースであって、XXバイトのリード(orライト)が完了するといった計測方法はないので、テスト用ファイルサイズの影響はあまりないかなと思っていたのだけど、最初適当に```-c10M```や```-c100M```などで検証していると、CrystalDiskMarkと比べて結果が良すぎる感じだった。影響のありそうなオプションをあれこれ試してると、```-cXX```の部分の影響がとても大きいことに気づいた。CrystalDiskMarkと同様の1GBを指定したところ、CrystalDiskMarkに近めの値を出すことができたので、理解できてないけど1GB設定にすることにした。書き込むバイト文字列をランダムにする```-Zr```などは、まったく影響がなかった。

上記のコマンドを実行すると以下のような結果となる

```
C:\>diskspd.exe -b4K -c1G -r -o1 -t1 -Sh -n -L -D -W0 -d10 -w0 testfile.dat

Command Line: diskspd.exe -b4K -c1G -r -o1 -t1 -Sh -n -L -D -W0 -d10 -w0 testfile.dat

Input parameters:

        timespan:   1
        -------------
        duration: 10s
        warm up time: 0s
        cool down time: 0s
        affinity disabled
        measuring latency
        gathering IOPS at intervals of 1000ms
        random seed: 0
        path: 'testfile.dat'
                think time: 0ms
                burst size: 0
                software cache disabled
                hardware write cache disabled, writethrough on
                performing read test
                block size: 4096
                using random I/O (alignment: 4096)
                number of outstanding I/O operations: 1
                thread stride size: 0
                threads per file: 1
                IO priority: normal

System information:

        computer name: xxxxxxxx
        start time: 2022/03/30 05:58:02 UTC

Results for timespan 1:
*******************************************************************************

actual test time:       10.00s
thread count:           1
proc count:             4

CPU |  Usage |  User  |  Kernel |  Idle
-------------------------------------------
   0|   2.18%|   1.25%|    0.94%|  97.82%
   1|   2.96%|   0.00%|    2.96%|  97.04%
   2|   2.18%|   1.09%|    1.09%|  97.82%
   3|   0.00%|   0.00%|    0.00%| 100.00%
-------------------------------------------
avg.|   1.83%|   0.59%|    1.25%|  98.17%

Total IO
thread |       bytes     |     I/Os     |    MiB/s   |  I/O per s |  AvgLat  | IopsStdDev | LatStdDev |  file
------------------------------------------------------------------------------------------------------------------
     0 |         6275072 |         1532 |       0.60 |     153.20 |    6.522 |       4.17 |     2.518 | testfile.dat (1024MiB)
------------------------------------------------------------------------------------------------------------------
total:           6275072 |         1532 |       0.60 |     153.20 |    6.522 |       4.17 |     2.518

Read IO
thread |       bytes     |     I/Os     |    MiB/s   |  I/O per s |  AvgLat  | IopsStdDev | LatStdDev |  file
------------------------------------------------------------------------------------------------------------------
     0 |         6275072 |         1532 |       0.60 |     153.20 |    6.522 |       4.17 |     2.518 | testfile.dat (1024MiB)
------------------------------------------------------------------------------------------------------------------
total:           6275072 |         1532 |       0.60 |     153.20 |    6.522 |       4.17 |     2.518

Write IO
thread |       bytes     |     I/Os     |    MiB/s   |  I/O per s |  AvgLat  | IopsStdDev | LatStdDev |  file
------------------------------------------------------------------------------------------------------------------
     0 |               0 |            0 |       0.00 |       0.00 |    0.000 |       0.00 |       N/A | testfile.dat (1024MiB)
------------------------------------------------------------------------------------------------------------------
total:                 0 |            0 |       0.00 |       0.00 |    0.000 |       0.00 |       N/A



total:
  %-ile |  Read (ms) | Write (ms) | Total (ms)
----------------------------------------------
    min |      0.393 |        N/A |      0.393
   25th |      4.483 |        N/A |      4.483
   50th |      6.567 |        N/A |      6.567
   75th |      8.652 |        N/A |      8.652
   90th |      9.882 |        N/A |      9.882
   95th |     10.313 |        N/A |     10.313
   99th |     10.880 |        N/A |     10.880
3-nines |     13.932 |        N/A |     13.932
4-nines |     22.049 |        N/A |     22.049
5-nines |     22.049 |        N/A |     22.049
6-nines |     22.049 |        N/A |     22.049
7-nines |     22.049 |        N/A |     22.049
8-nines |     22.049 |        N/A |     22.049
9-nines |     22.049 |        N/A |     22.049
    max |     22.049 |        N/A |     22.049
```

# 各ツールでの計測結果

各ツールでの計測結果をまとめると、以下のようになる
共通設定部分は、ブロックサイズ4KBのランダムアクセス

| スループット(MB/s) | winsat | CrystalDiskMark | diskspd |
|:-:|:-:|:-:|:-:|
| ランダムリード  | 0.37(Q?) | 0.39(Q1), 0.95(Q32) | 0.60(Q1), 1.68(Q32) |
| ランダムライト  | 6.01(Q?) | 0.79(Q1), 0.84(Q32) | 1.47(Q1), 1.62(Q32)|

| IOPS | winsat | CrystalDiskMark | diskspd |
|:-:|:-:|:-:|:-:|
| ランダムリード  | 測定不可 | 95(Q1), 232(Q32) | 153(Q1), 429(Q32) |
| ランダムライト  | 測定不可 | 191(Q1), 205(Q32) | 375(Q1), 413(Q32) |

ツールによって値が異なるのと、実行するタイミングによっても値が上下するので、繰り返しの測定は必要(自分が完全に掌握してるマシンで無風状態と分かってるならまだいいんだけど共有環境は特に注意)。
また、計測にあたって、検証環境は低スペックなマシンで行ったが、一方、SSDを積んだマシンでも同様の傾向か確認する必要がある。
などなど前提や注意事項は色々付随してくるんだけど、今回のように各ツールの特性を調査してある程度傾向が分かってくれば、あとはどんどん実施して、ナレッジ蓄積と測定手法の改善サイクルを回していけるといいなと思う

# diskspdを正しく理解するための参考リンク

MSの紹介記事読む。「キー パラメーターを指定する」の項で主要な幾つかのオプションについて分かりやすく書かれている
https://docs.microsoft.com/ja-jp/azure-stack/hci/manage/diskspd-overview

diskspdのコードリポジトリ。```Code``` ```Download Zip```からダウンロードする。
https://github.com/microsoft/diskspd

コマンドラインリファレンス
https://github.com/Microsoft/diskspd/wiki/Command-line-and-parameters

各オプションを検討するときに読む
https://github.com/Microsoft/diskspd/wiki/Customizing-tests

# キューデプスの指定について

キューデプス(```-o1/-o32```)の指定で結構パフォーマンス変わるけど、現在のサーバならNCQ対応してること前提で```-o32```決め打ちで良いのでは..?感はある。また、HDDならではのシークタイム/回転待ち時間の効率化ならSSDでは気にしなくて良いのではと思うが[SSDでもパフォーマンスに貢献してる](https://kb-jp.sandisk.com/app/answers/detail/a_id/8977/~/%E3%83%8D%E3%82%A4%E3%83%86%E3%82%A3%E3%83%96%E3%82%B3%E3%83%9E%E3%83%B3%E3%83%89%E3%82%AD%E3%83%A5%E3%83%BC%E3%82%A4%E3%83%B3%E3%82%B0%28ncq%29%E3%81%AE%E5%AE%9A%E7%BE%A9%E3%81%AB%E3%81%A4%E3%81%84%E3%81%A6)ということでここの指定はとても微妙。悩むなら両方指定して測っておくか、という感じ。

# CrystalDiskMarkとdiskspdで結果が違う点について

CrystalDiskMarkはdiskspdをラッパーしてるのだから、オプション指定などが一緒なら、どちらも同じ結果がでるはずと思ってた(結果が違うなら、オプション指定などで差があるのでは)。しかし、実際にはCrystalDiskMarkの方が数値が低い。オプションの指定に差異があるのではと、CrystalDiskMarkの設定と、diskspdのオプションを見比べて色々試してみるんだけど、結局同じ値を出すことはできなかった。CrystalDiskMarkの設定を意識したオプションとしては、```-c1G``` ```-o1[-o32]```あたり。上にも書いたが、特に```-c1G```の指定が影響が大きい。ここは判然としないのだが各ツールの特性として覚えておこうという感じ。

# 余談

diskspdの使用例について海外のサイトを調べているときに(どのサイトだったか忘れてしまったのですが)、一つのディスクにCとDドライブが構成されているとして、机上ではCドライブの方が高速となることを考慮する、と書いてあるのを見てなるほどーとなりました。ディスクは外周の方が高速で、外周から利用されるので、ディスクの前の方にあるCドライブの方が高速になる、と。ただ、ディスクをフォーマットするときにクイックフォーマットする場合は、これは当てはまらないんだろうなあと思います。完全フォーマット(全セクタ0埋め)する場合は、各セクタがどのドライブ用かはあらかじめ割り当てられていることになるので、外周に配置されるCドライブ内のファイルの方が速そうです。CrystalDiskMarkでフォルダを指定する設定があるのはその辺が理由かなと思います。ただしこれは共有ストレージやクラウドを使ってる場合は、ストレージ層は抽象化されていて、OSから見たセクターの位置と実際のストレージのディスク上のそれは一致していないことが多いと思うので、ディスクの外周とか気にするのはナンセンスになりそうです。