スポンサーリンク

Linux でディスクのアクセス速度を測定する その3 【fio編】

snow_mountain
記事内に広告が含まれています。
スポンサーリンク

はじめに

Synology の DS218plus の HDD を 4TB -> 8TB に更新した際に、内蔵2基 + Hyper Backup 用1基の合計3基 (SG, WD1, WD2) の 4TB HDD が遊休となりました。これらの HDD を使用して、Linux の様々なファイルシステムの違いや Software RAID を組んだ場合のディスクのアクセス速度を測定しようと考えています。

前回の記事「その2」では、Linux の Ext4 ファイルシステムで diskspd-for-linux を使用して、ディスクのアクセス速度を測定してみました。

その結果、アクセス速度の測定結果のバラツキが大きく、再現性もない為に、

diskspd for linux はディスクのアクセス速度の評価には使用できない

と判断しました。バラツキを抑えたり再現性のある条件を見つけることができませんでした。

今回は、ディスクのアクセス速度を測定するもう一つのソフトウェアである fio を使用して測定してみました。測定条件は、Windows 版の CrystalDiskMark (以下 CDM と略称) や前回の diskspd-for-linux にできるだけ合わせるようにしています。

2023年12月26日追記:

その4です。fio で filesystem 4種類 (Ext4, XFS, Btrfs, JFS) のアクセス速度を比較しました。

2024年1月19日追記:

その5です。mdadm で RAID 0 (2, 3基) / RAID 1 / RAID 5 の RAID array を作成し、3種類 (Ext4, XFS, Btrfs) の filesystem のアクセス速度を比較しました。

スポンサーリンク

fio について

「その1」の記事内でも fio について簡単に記載しました。

fio は Linux で一般的に使用されているディスクのアクセス速度を測定するソフトウェアです。

非常に多くのパラメータを指定できますが、今回使用したものは

  • ioengine には libaio を使用
  • Sequential / Random read / write
  • Data size 250MB ~ 10GB
  • startdelay (ディスクアクセスを始めるまでの時間)
  • ramp_time (ディスクアクセスを開始してから測定を始めるまでの時間)
  • terse 形式での出力 (';' でセパレートされた CSV のような形式)

です。

CDM に似た条件で fio で測定する方法

fio では、オプションで条件を指定する以外に、jobfile と呼ばれる設定ファイルに指定するオプションを予め記載しておいて、その中から指定の条件で測定する機能があります。

CDM に合わせて、且つ下記に記載した比較的安定して測定できる条件を記載した設定ファイルを以下に示します。仮に fio_setting.fio とします。

; fio_setting.fio
[global]
ioengine=libaio
direct=1
stonewall
group_reporting
size=3g
runtime=60

[SEQ1MQ8T1_READ]
startdelay=5
ramp_time=5
rw=read
bs=1m
iodepth=8
numjobs=1

[SEQ1MQ8T1_WRITE]
startdelay=5
ramp_time=20
rw=write
bs=1m
iodepth=8
numjobs=1

[SEQ1MQ1T1_READ]
startdelay=5
ramp_time=5
rw=read
bs=1m
iodepth=1
numjobs=1

[SEQ1MQ1T1_WRITE]
startdelay=5
ramp_time=20
rw=write
bs=1m
iodepth=1
numjobs=1

[RND4KQ32T1_READ]
startdelay=5
ramp_time=5
rw=randread
bs=4k
iodepth=32
numjobs=1

[RND4KQ32T1_WRITE]
startdelay=20
ramp_time=5
rw=randwrite
bs=4k
iodepth=32
numjobs=1

[RND4KQ1T1_READ]
startdelay=5
ramp_time=5
rw=randread
bs=4k
iodepth=1
numjobs=1

[RND4KQ1T1_WRITE]
startdelay=30
ramp_time=5
rw=randwrite
bs=4k
iodepth=1
numjobs=1

[global] セクションには共通設定を、各セクションには個々の条件を指定します。startdelay や ramp_time を個々のセクションで変更していますが、アクセス速度を安定させる為の条件です。

例えば SEQ1MQ8T1_READ の条件で測定する場合には、以下のように実行します。--directory オプションで測定対象のディレクトリを、--section オプションで fio_setting.fio 内の測定する条件を指定します。最後に設定ファイルの fio_setting.fio を指定します。

$ fio --directory="/media/hiro/SG/" --section="SEQ1MQ8T1_READ" ./fio_setting.fio
SEQ1MQ8T1_READ: (g=0): rw=read, bs=(R) 1024KiB-1024KiB, (W) 1024KiB-1024KiB, (T) 1024KiB-1024KiB, ioengine=libaio, iodepth=8
fio-3.28
Starting 1 process
Jobs: 1 (f=1): [R(1)][82.1%][r=169MiB/s][r=169 IOPS][eta 00m:05s]
SEQ1MQ8T1_READ: (groupid=0, jobs=1): err= 0: pid=4965: Sat Nov 18 22:07:34 2023
  read: IOPS=175, BW=177MiB/s (185MB/s)(2205MiB/12491msec)
    slat (usec): min=38, max=178, avg=48.61, stdev=10.15
    clat (msec): min=6, max=561, avg=45.33, stdev=22.29
     lat (msec): min=6, max=561, avg=45.38, stdev=22.29
    clat percentiles (msec):
     |  1.00th=[   36],  5.00th=[   36], 10.00th=[   38], 20.00th=[   41],
     | 30.00th=[   41], 40.00th=[   42], 50.00th=[   44], 60.00th=[   44],
     | 70.00th=[   48], 80.00th=[   48], 90.00th=[   52], 95.00th=[   52],
     | 99.00th=[   95], 99.50th=[  107], 99.90th=[  558], 99.95th=[  558],
     | 99.99th=[  558]
   bw (  KiB/s): min=161792, max=197002, per=100.00%, avg=181425.25, stdev=11081.64, samples=24
   iops        : min=  158, max=  192, avg=177.08, stdev=10.79, samples=24
  lat (msec)   : 10=0.14%, 20=0.09%, 50=83.58%, 100=15.74%, 250=0.59%
  lat (msec)   : 500=0.05%, 750=0.14%
  cpu          : usr=0.06%, sys=1.01%, ctx=2205, majf=0, minf=58
  IO depths    : 1=0.0%, 2=0.0%, 4=0.0%, 8=100.0%, 16=0.0%, 32=0.0%, >=64=0.0%
     submit    : 0=0.0%, 4=100.0%, 8=0.0%, 16=0.0%, 32=0.0%, 64=0.0%, >=64=0.0%
     complete  : 0=0.0%, 4=100.0%, 8=0.1%, 16=0.0%, 32=0.0%, 64=0.0%, >=64=0.0%
     issued rwts: total=2198,0,0,0 short=0,0,0,0 dropped=0,0,0,0
     latency   : target=0, window=0, percentile=100.00%, depth=8

Run status group 0 (all jobs):
   READ: bw=177MiB/s (185MB/s), 177MiB/s-177MiB/s (185MB/s-185MB/s), io=2205MiB (2312MB), run=12491-12491msec

Disk stats (read/write):
  sdb: ios=3032/0, merge=0/0, ticks=137670/0, in_queue=137670, util=99.49%

測定する条件がある程度決まっていて、単独で実行する場合には jobfile を作成しておくと便利です。

今回の測定に使用した script

fio の測定結果を安定させる条件を探る為に、startdelay や ramp_time オプションを変更して複数回の測定を行う必要がありました。

作業し易くする為に、簡単な script を作成しました。fio と awk があれば動作します。名称は仮に fio_measure.sh としました。簡単なエラーチェックも行っています。

#! /bin/bash

# fio は apt で入れる
FIO="fio"

# 引数の数チェック : 8
if [ "$#" != 8 ]
then
  echo "ERROR!"
  echo "You must provice five args."
  echo " 1: Measurement Type : SEQ1MQ8T1, ..."
  echo " 2: Data Size : 250M, 500m, 1000M or 1G, 3G, 5G, 8G, 10G."
  echo " 3: Target directory : /media/user/target/  etc."
  echo " 4: Counts : Repeat Measuring times."
  echo " 5: READ or WRITE : Measurement Type."
  echo " 6: startdelay parameter."
  echo " 7: ramp_time parameter."
  echo " 8: runtime parameter."
  exit 1
fi

# CrystalDiskMark 同等の測定方法の指定
# パラメータを変更したい場合には case ... esac をコメント
TYPE=$1
case ${TYPE} in
  "SEQ1MQ8T1" )
    ;;
  "SEQ1MQ1T1" )
    ;;
  "RND4KQ32T1" )
    ;;
  "RND4KQ1T1" )
    ;;
  *)
    echo "ERROR!"
    echo "Type Miss match. Do not use Type ${TYPE}"
    exit 1
esac

# Data size の指定
D_SIZE=$2
case ${D_SIZE} in
  "250M" )
    ;;
  "500M" )
    ;;
  "1000M" )
    ;;
  "1G" )
    ;;
  "3G" )
    ;;
  "5G" )
    ;;
  "8G" )
    ;;
  "10G" )
    ;;
  *)
    echo "ERROR!"
    echo "Data Size Miss match. Do not use Data Size ${D_SIZE}"
    exit 1
esac

# Target Directory の指定
TGT=$3
TEST_DIR=$3
DIR_NAME=`basename ${TEST_DIR}`

# Directory の存在確認
if [ ! -d ${TEST_DIR} ]
then
  echo "ERROR!"
  echo "Data Directory not exists."
  exit 1
fi

# 測定回数の指定
COUNTS=$4
if [ ${COUNTS} -lt 1 ]  # 1以上を指定
then
  echo "ERROR!"
  echo "Measure counts >=1."
  exit 1
fi

# Read / Write の指定
R_W=$5
if [ ${R_W} = "READ" ]
then
  w_OP="read"
else
  w_OP="write"
fi

# CrystalDiskMark の文字列から測定条件を抜き出す
b_OP=`echo ${TYPE:3:2}`  # 4〜5文字が Block size
case ${b_OP} in
  "1M" )
    ;;
  "4K" )
    ;;
  *)
    echo "ERROR!"
    echo "Type Miss matcch. Set 1M or 4K."
    exit 1
esac

TEMP_OP=`echo ${TYPE##*Q}`  # Q は2桁もあるので、Qから前とTから後を消去
o_OP=`echo ${TEMP_OP%T*}`
if [ ${o_OP} -lt 1 ]  # 1以上を指定
then
  echo "ERROR!"
  echo "Q parameter must Q>=1."
  exit 1
fi

t_OP=`echo ${TYPE#*T}`  # Tから前を消去
if [ ${t_OP} -lt 1 ]  # 1以上を指定
then
  echo "ERROR!"
  echo "t parameter must t>=1."
  exit 1
fi


SDELAY=$6  # startdelay
if [ ${SDELAY} -lt 0 ]
then
  echo "ERROR!"
  echo "startdelay parameter must >=0."
  exit 1
fi

RAMPTIME=$7  # rump_time
if [ ${RAMPTIME} -lt 0 ]  # 1以上を指定
then
  echo "ERROR!"
  echo "rump_time parameter must >=0."
  exit 1
fi

RUNTIME=$8  # runtime
if [ ${RUNTIME} -lt 1 ]  # 1以上を指定
then
  echo "ERROR!"
  echo "runtime parameter must >=1."
  exit 1
fi


M_TYPE=`echo ${TYPE:0:3}`  # 最初の3文字が SEQ or RND
if [ ${M_TYPE} = "SEQ" ]  # -r : Random  none : Sequential
then
  r_OP=""
else
  r_OP="rand"
fi

# CSV で出力

# title line
echo -n "No.,Type,Read/Write,Target,Data_size,Pattern,BlockSize,IO_depth(Q),Threads(T),"
echo "StartDelay,RampTime,RunTime,result"

for i in `seq ${COUNTS}`; do
  echo -n "${i},${TYPE},${R_W},${DIR_NAME},${D_SIZE},${M_TYPE},${b_OP},${o_OP},${t_OP},"
  echo -n "${SDELAY},${RAMPTIME},${RUNTIME},"
  # measure
  ${FIO} --directory=${TEST_DIR} --ioengine=libaio --direct=1 --stonewall \
	--group_reporting=1 --name=${TYPE}_${R_W} \
	--size=${D_SIZE,,} --startdelay=${SDELAY} --ramp_time=${RAMPTIME} --runtime=${RUNTIME} \
	--rw=${r_OP}${w_OP} --bs=${b_OP,,} \
	--iodepth=${o_OP} --numjobs=${t_OP} \
	--output-format=terse | awk -F ';' '{print ($7+$48) / 1000 }'
done

実行例です。

$ ./fio_measure.sh SEQ1MQ8T1 3G /media/hiro/SG/ 2 READ 5 5 60
No.,Type,Read/Write,Target,Data_size,Pattern,BlockSize,IO_depth(Q),Threads(T),StartDelay,RampTime,RunTime,result
1,SEQ1MQ8T1,READ,SG,3G,SEQ,1M,8,1,5,5,60,180.667
2,SEQ1MQ8T1,READ,SG,3G,SEQ,1M,8,1,5,5,60,183.297

CSV で出力するようにしましたので、Excel に取り込んで集計しました。

測定条件と測定結果

前記の script を使用して、fio でアクセス速度を安定して測定できる条件を試行しました。約6600回の試行結果です。

測定条件

fio でアクセス速度を安定させる為には、

  • startdelay (fio を実行してからディスクにアクセスするまでの時間) の設定
  • ramp_time (ディスクにアクセスしてから測定を始めるまでの時間) の設定
  • 測定に使用するデータサイズを適切に設定

が必要です。測定時間 (runtime) を伸ばしても測定結果のバラツキは減少しませんでしたので、60秒で固定しました。

ちなみに、fio の runtime は、(1) runtime 未満でデータサイズ全体の読み書きが完了したらその時点で終了、(2) データの読み書きが終わらない場合には runtime の時間で打ち切り、というアルゴリズムになっています。少量のデータサイズの場合には runtime に設定した時間に到達せずに測定が終了します。Sequential Read 180 MB/sec の HDD の場合には、10GB のデータサイズで約60秒で読み込みが終了します。

startdelay と ramp_time については、試行錯誤した結果、以下の条件で測定しました。

条件Read / Writestartdelayramp_timeruntime
SEQ1MQ8T1Read5560
SEQ1MQ8T1Write52060
SEQ1MQ1T1Read5560
SEQ1MQ1T1Write52060
RND4KQ32T1Read5560
RND4KQ32T1Write20560
RND4KQ1T1Read5560
RND4KQ1T1Write30560
fio の測定条件 (startdelay, ramp_time)

測定結果のバラツキのパターンは二種類あります。

  1. 複数回のうち1回~2回だけ低い結果が出る
  2. 開始直後が高い or 低い値から徐々に落ち着く

startdelay と ramp_time を振ってみて、この二つの現象が発生しなくなる条件を探しました。

fio は測定開始時にデータファイルが存在しない場合には、新規に作成してから測定を開始します。データサイズを変更した際には、データファイルを再作成するようなのですが、大きなサイズで作成した後に小さいサイズで測定するような場合には、再作成せずに測定しているようでした。ですので、データサイズを変更した場合には、データファイルを削除してから測定した方が、測定結果は安定すると思います。(この理由もあり 21回測定して最初の測定結果を除外しました)

データサイズは、測定結果及びバラツキに影響します。以下では、CDM 相当の条件で、データサイズを変更してディスクのアクセス速度を測定した結果について説明します。

それぞれの条件について、21回連続で測定して1番目のデータを除外し、n=20 のデータを使用しています。最初のグラフがアクセス速度の平均値、二つ目のグラフがバラツキを示す R / Avg. % になります。

SEQ1MQ8T1 Read / Write

SEQ1MQ8T1 の Read の結果です。

データサイズ 3GB 以上でバラツキが小さくなり、データサイズを増やしてもアクセス速度の変化が少なくなりました。

データサイズ 1GB 未満では、startdelay を 20 sec にする事でバラツキを少し抑える事ができました。とはいえ、データサイズ 3GB 以上のような 1%未満にバラツキを抑える事は出来ませんでした。少量のデータサイズの場合には、読み書き完了までの時間が短いので、バラツキを少なくする事は難しいのかもしれません。

SEQ1MQ8T1 Read Access Rate [MB/sec]
SEQ1MQ8T1 Read Access Rate [MB/sec]
SEQ1MQ8T1 Read R / Avg. [%]
SEQ1MQ8T1 Read R / Avg. [%]

SEQ1MQ8T1 の Write の結果です。Read と同様にデータサイズ 3GB 以上で落ち着きました。RampTime = 5 のデータサイズ 1GB でのアクセス速度の低さは、複数回測定しましたが同様の結果でした。原因は分かりません。SG の R / Avg. が 4% を超えていますが、±2% と考えれば、十分再現性のある結果かと思います。

SEQ1MQ8T1 Write Access Rate [MB/sec]
SEQ1MQ8T1 Write Access Rate [MB/sec]
SEQ1MQ8T1 Write R / Avg. [%]
SEQ1MQ8T1 Write R / Avg. [%]

SEQ1MQ1T1 Read / Write

SEQ1MQ1T1 の Read の結果です。こちらもデータサイズ 3GB 以上で安定しました。逆に 1GB でバラツキが大きい原因はよく分かりません。試しに 1000 MB でも実行しましたが、バラツキは抑えられませんでした。

SEQ1MQ1T1 Read Access Rate [MB/sec]
SEQ1MQ1T1 Read Access Rate [MB/sec]
SEQ1MQ1T1 Read R / Avg. [%]
SEQ1MQ1T1 Read R / Avg. [%]

SEQ1MQ1T1 の Write の結果です。全体的にバラツキは小さいですが、特にデータサイズ 3GB 以上で安定しています。

SEQ1MQ1T1 Write Access Rate [MB/sec]
SEQ1MQ1T1 Write Access Rate [MB/sec]
SEQ1MQ1T1 Write R / Avg. [%]
SEQ1MQ1T1 Write R / Avg. [%]

RND4KQ32T1 Read / Write

RND4KQ32T1 の Read の結果です。RND4KQ32T1 の Read の結果のみ、データサイズが増加するにしたがってディスクのアクセス速度が低下しています。バラツキは全領域で少なめです。

データサイズ 1GB ~ 10GB のディスクのアクセス速度の平均値がほぼ 3GB のアクセス速度となりますので、RND4KQ32T1 の Read の代表値として、データサイズ 3GB の結果を使用する事にします。

RND4KQ32T1 Read Access Rate [MB/sec]
RND4KQ32T1 Read Access Rate [MB/sec]
RND4KQ32T1 Read R / Avg. [%]
RND4KQ32T1 Read R / Avg. [%]

RND4KQ32T1 の Write の結果です。Read とは異なり、3GB 以上ではアクセス速度は変化しません。

RND4KQ32T1 Write Access Rate [MB/sec]
RND4KQ32T1 Write Access Rate [MB/sec]
RND4KQ32T1 Write R / Avg. [%]
RND4KQ32T1 Write R / Avg. [%]

RND4KQ1T1 Read / Write

RND4KQ1T1 の Read の結果です。データサイズ 3GB 以上ではディスクのアクセス速度は変わりません。バラツキも小さめです。

RND4KQ1T1 Read Access Rate [MB/sec]
RND4KQ1T1 Read Access Rate [MB/sec]
RND4KQ1T1 Read R / Avg. [%]
RND4KQ1T1 Read R / Avg. [%]

RND4KQ1T1 の Write の結果です。バラツキは他の結果と比べると多く 4~8%となっています。ディスクのアクセス速度の平均値は、3GB 以上ではほぼ変わりません。

RND4KQ1T1 Write Access Rate [MB/sec]
RND4KQ1T1 Write Access Rate [MB/sec]
RND4KQ1T1 Write R / Avg. [%]
RND4KQ1T1 Write R / Avg. [%]

データサイズ 3GB の結果一覧

データサイズが 3GB 以上でディスクのアクセス速度のバラツキが小さくなる事が分かりました。この時の結果について一覧にまとめます。

条件Read / WriteAvg.
[MB/sec]
Max.
[MB/sec]
Min.
[MB/sec]
R
[MB/sec]
R / Avg.
[%]
SEQ1MQ8T1Read183.5184.0182.31.70.9
SEQ1MQ8T1Write170.2173.6165.77.94.6
SEQ1MQ1T1Read185.1185.6183.91.70.9
SEQ1MQ1T1Write174.7178.5172.26.33.6
RND4KQ32T1Read1.541.551.530.021.2
RND4KQ32T1Write1.331.341.320.021.6
RND4KQ1T1Read0.510.520.510.010.8
RND4KQ1T1Write1.301.321.270.053.9
データサイズ 3GB の時の結果一覧 (SG)
条件Read / WriteAvg.
[MB/sec]
Max.
[MB/sec]
Min.
[MB/sec]
R
[MB/sec]
R / Avg.
[%]
SEQ1MQ8T1Read179.1180.1178.31.81.0
SEQ1MQ8T1Write174.7174.9174.20.70.4
SEQ1MQ1T1Read179.3180.4178.32.11.2
SEQ1MQ1T1Write177.9178.3177.01.30.7
RND4KQ32T1Read2.142.142.130.010.5
RND4KQ32T1Write2.042.092.000.094.5
RND4KQ1T1Read0.520.520.510.011.4
RND4KQ1T1Write2.042.071.980.094.4
データサイズ 3GB の時の結果一覧 (WD1)
条件Read / WriteAvg.
[MB/sec]
Max.
[MB/sec]
Min.
[MB/sec]
R
[MB/sec]
R / Avg.
[%]
SEQ1MQ8T1Read182.9183.0181.91.10.6
SEQ1MQ8T1Write174.7174.9174.20.70.4
SEQ1MQ1T1Read182.5183.0181.51.50.9
SEQ1MQ1T1Write176.7177.2175.81.40.8
RND4KQ32T1Read2.152.152.130.020.9
RND4KQ32T1Write2.012.061.970.094.6
RND4KQ1T1Read0.520.520.510.011.2
RND4KQ1T1Write1.871.941.810.136.9
データサイズ 3GB の時の結果一覧 (WD2)

過去に Windows10 の NTFS で CDM で測定した結果と、多少の違いはあれど概ね同様の結果が得られました。

今回はディスクのアクセス速度のバラツキを見る為に n=20 で平均値と R / Avg. を調べましたが、バラツキ具合が分かりましたので、今後は試行回数を減らす、或いは平均ではなく最大値を使用しても、比較結果の判断に利用できると感じました。

今後は、この Ext4 の結果を元に、ファイルシステムの違いや RAID の影響を調べてみようと考えています。

まとめ

3基の HDD (Seagate, WesternDigital x 2) について、Ext4 ファイルシステムで fio を使用してディスクのアクセス速度を測定しました。

  • Windows 版 CDM と同様の条件で測定し、多少の違いはありますが概ね同様の結果が得られました。
  • データサイズが 3GB 以上の条件では diskspd for linux と比べてバラツキが小さく、再現性の高い結果が得られました。
  • startdelay と ramp_time オプションを適切に設定する必要がありました。runtime を伸ばしてもばらつきは小さくなりませんでした。特に Write に関しては最適値があり、大きくしてもばらつきは小さくなりませんでした。

今回の Ext4 の結果を元に、他のファイルシステムについても測定し比較したいと考えています。ファイルシステム毎に特徴がありますので、それについても紹介できたらと考えています。

今回のアイキャッチ画像

SDXL で冬山を生成しました。北国の生まれ育ちなので、そろそろスタッドレスタイヤに取り替える時期を考えている季節です。冬道も昔は平気でしたが今は無理です。そもそもスタッドレスタイヤを持っていないので、車では雪が降ったら外出しません。

コメント

タイトルとURLをコピーしました