SSD性能测试第一神器:FIO

对于SSD性能测试来说,最好的工具莫过于FIO了。

上面这个可爱的小伙子名字叫Jens Axboe,他是丹麦哥本哈根大学计算机系没毕业的学生,他还有一个有名的同乡叫Linus,没想到老乡后来也成了他的领导。Jens今年(2017年)40岁,16岁开始就接触Linux,后来也成了Linux开发者,现在是Linux Kernel大拿了,负责块设备层的维护。这个块设备层就是跟我们SSD关系最紧密的层级,联系了上层文件系统和下层设备驱动程序。他开发了不少有用的程序,比如Linux IO Scheduler里面的Deadline, CFQ Scheduler,还有著名的王牌测试工具FIO。 Jens曾经在Fusion-IO、Oracle等公司工作,现在在Facebook,阿呆听说在硅谷Facebook给码农的薪水是最高的。

FIO是Jens开发的一个开源测试工具,功能非常强大,本文就只介绍其中一些基本功能。

线程,队列深度,Offset,同步异步,DirectIO,BIO

使用FIO之前,首先要有一些SSD性能测试的基础知识。

线程指的是同时有多少个读或写任务在并行执行,一般来说,CPU里面的一个核心同一时间只能运行一个线程。如果只有一个核心,要想运行多线程,只能使用时间切片,每个线程跑一段时间片,所有线程轮流使用这个核心。Linux使用Jiffies来代表一秒钟被划分成了多少个时间片,一般来说Jiffies是1000或100,所以时间片就是1毫秒或10毫秒。

一般电脑发送一个读写命令到SSD只需要几微秒,但是SSD要花几百微秒甚至几毫秒才能执行完这个命令。如果发一个读写命令,然后线程一直休眠,等待结果回来才唤醒处理结果,这种叫做同步模式。可以想象,同步模式是很浪费SSD性能的,因为SSD里面有很多并行单元,比如一般企业级SSD内部有8-16个数据通道,每个通道内部有4-16个并行逻辑单元(LUN,Plane),所以同一时间可以执行32-256个读写命令。同步模式就意味着,只有其中一个并行单元在工作,暴殄天物。

为了提高并行性,大部分情况下SSD读写采用的是异步模式。就是用几微秒发送命令,发完线程不会傻傻的在那里等,而是继续发后面的命令。如果前面的命令执行完了,SSD通知会通过中断或者轮询等方式告诉CPU,CPU来调用该命令的回调函数来处理结果。这样的好处是,SSD里面几十上百个并行单元大家都能分到活干,效率暴增。

不过,在异步模式下,CPU不能一直无限的发命令到SSD。比如SSD执行读写如果发生了卡顿,那有可能系统会一直不停的发命令,几千个,甚至几万个,这样一方面SSD扛不住,另一方面这么多命令会很占内存,系统也要挂掉了。这样,就带来一个参数叫做队列深度。举个例子,队列深度64就是说,系统发的命令都发到一个大小为64的队列,如果填满了就不能再发。等前面的读写命令执行完了,队列里面空出位置来,才能继续填命令。

一个SSD或者文件有大小,测试读写的时候设置Offset就可以从某个偏移地址开始测试。比如从offset=4G的偏移地址开始。

Linux读写的时候,内核维护了缓存,数据先写到缓存,后面再后台写到SSD。读的时候也优先读缓存里的数据。这样速度可以加快,但是一旦掉电缓存里的数据就没了。所以有一种模式叫做DirectIO,跳过缓存,直接读写SSD。

Linux读写SSD等块设备使用的是BIO,Block-IO,这是个数据结构,包含了数据块的逻辑地址LBA,数据大小和内存地址等。

FIO初体验

一般Linux系统是自带FIO的,如果没有或者版本太老,要自己从https://github.com/axboe/fio 下载最新版本源代码编译安装。进入代码主目录,输入下列命令就编译安装好了。

 

 


1



./configure;make&&makeinstall


 

帮助文档用下面命令查看:

 

 


1



manfio


 

先来看一个简单的例子:

 

 


1



fio-rw=randwrite-ioengine=libaio-direct=1threadnumjobs=1-iodepth=64-filename=/dev/sdb4-size=10G-name=job1-offset=0MB-bs=4k-name=job2-offset=10G-bs=512--output TestResult.log


 

每一项的意思都可以从fio帮助文档是:
fio:软件名称。
-rw=randwrite:读写模式,randwrite是随机写测试,还有顺序读read,顺序写write,随机读randread,混合读写等。
-ioengine=libaio:libaio指的是异步模式,如果是同步就要用sync。
-direct=1:是否使用directIO。
-thread:使用pthread_create创建线程,另一种是fork创建进程。进程的开销比线程要大,一般都采用thread测试。
–numjobs=1:每个job是1个线程,这里用了几,后面每个用-name指定的任务就开几个线程测试。所以最终线程数=任务数* numjobs。
-iodepth=64:队列深度64.
-filename=/dev/sdb4:数据写到/dev/sdb4这个盘(块设备)。这里可以是一个文件名,也可以是分区或者SSD。也可以写一个目录里面的多个文件。
-size=10G:每个线程写入数据量是10GB。
-name=job1:一个任务的名字,名字随便起,重复了也没关系。这个例子指定了job1和job2,建立了两个任务,共享-name=job1之前的参数。-name之后的就是这个任务独有的参数。
-offset=0MB:从偏移地址0MB开始写。
-bs=4k:每一个BIO命令包含的数据大小是4KB。一般4KB IOPS测试,就是在这里设置。
–output TestResult.log:日志输出到TestResult.log。

FIO结果解析

我们来看一个FIO测试随机读的结果。命令如下,2个任务并行测试,队列深度64,异步模式,每个任务测试数据1GB,每个数据块4KB。所以,这个命令是在测试两个线程、队列深度64下的4KB随机读IOPS。

 

 


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48



# fio -rw=randread -ioengine=libaio -direct=1  -iodepth=64 -filename=/dev/sdc -size=1G -bs=4k -name=job1 -offset=0G -name=job2 -offset=10G

job1:(g=0):rw=randread,bs=4K-4K/4K-4K/4K-4K,ioengine=libaio,iodepth=64

job2:(g=0):rw=randread,bs=4K-4K/4K-4K/4K-4K,ioengine=libaio,iodepth=64

fio-2.13

Starting2processes

Jobs:2(f=2)

job1:(groupid=0,jobs=1):err=0:pid=27752:Fri Jul2814:16:502017

read:io=1024.0MB,bw=392284KB/s,iops=98071,runt=2673msec

slat(usec):min=6,max=79,avg=9.05,stdev=2.04

clat(usec):min=148,max=1371,avg=642.89,stdev=95.08

lat(usec):min=157,max=1380,avg=651.94,stdev=95.16

clat percentiles(usec):

|1.00th=[438],5.00th=[486],10.00th=[516],20.00th=[564],

|30.00th=[596],40.00th=[620],50.00th=[644],60.00th=[668],

|70.00th=[692],80.00th=[724],90.00th=[756],95.00th=[796],

|99.00th=[884],99.50th=[924],99.90th=[1004],99.95th=[1048],

|99.99th=[1144]

lat(usec):250=0.01%,500=6.82%,750=81.14%,1000=11.93%

lat(msec):2=0.11%

cpu:usr=9.09%,sys=90.08%,ctx=304,majf=0,minf=98

IO depths:1=0.1%,2=0.1%,4=0.1%,8=0.1%,16=0.1%,32=0.1%,>=64=100.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.0%,16=0.0%,32=0.0%,64=0.1%,>=64=0.0%

issued:total=r=262144/w=0/d=0,short=r=0/w=0/d=0,drop=r=0/w=0/d=0

latency:target=0,window=0,percentile=100.00%,depth=64

job2:(groupid=0,jobs=1):err=0:pid=27753:Fri Jul2814:16:502017

read:io=1024.0MB,bw=447918KB/s,iops=111979,runt=2341msec

slat(usec):min=5,max=41,avg=6.30,stdev=0.79

clat(usec):min=153,max=1324,avg=564.61,stdev=100.40

lat(usec):min=159,max=1331,avg=570.90,stdev=100.41

clat percentiles(usec):

|1.00th=[354],5.00th=[398],10.00th=[430],20.00th=[474],

|30.00th=[510],40.00th=[540],50.00th=[572],60.00th=[596],

|70.00th=[620],80.00th=[644],90.00th=[684],95.00th=[724],

|99.00th=[804],99.50th=[844],99.90th=[932],99.95th=[972],

|99.99th=[1096]

lat(usec):250=0.03%,500=27.57%,750=69.57%,1000=2.79%

lat(msec):2=0.04%

cpu:usr=11.62%,sys=75.60%,ctx=35363,majf=0,minf=99

IO depths:1=0.1%,2=0.1%,4=0.1%,8=0.1%,16=0.1%,32=0.1%,>=64=100.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.0%,16=0.0%,32=0.0%,64=0.1%,>=64=0.0%

issued:total=r=262144/w=0/d=0,short=r=0/w=0/d=0,drop=r=0/w=0/d=0

latency:target=0,window=0,percentile=100.00%,depth=64

Run status group0(all jobs):

READ:io=2048.0MB,aggrb=784568KB/s,minb=392284KB/s,maxb=447917KB/s,mint=2341msec,maxt=2673msec

Disk stats(read/write):

sdc:ios=521225/0,merge=0/0,ticks=277357/0,in_queue=18446744073705613924,util=100.00%


 

FIO会为每个Job打印统计信息。最后面是合计的数值。我们一般看重的总的性能和延迟。

首先看的是最后总的带宽,aggrb=784568KB/s,算成4KB就是196K IOPS。

再来看看延迟Latency。Slat是发命令时间,slat (usec): min=6, max=79, avg= 9.05, stdev= 2.04说明最短时间6微秒,最长79微秒,平均9微秒,标准差2.04。clat是命令执行时间,lat就是总的延迟。看得出来,读的平均延迟在571微秒左右。

clat percentiles (usec)给出了延迟的统计分布。比如90.00th=[ 684]说明90%的读命令延迟都在684微秒以内。

用FIO做数据校验

用FIO可以检验写入数据是否出错。用-verify=str来选择校验算法,有md5 crc16 crc32 crc32c crc32c-intel crc64 crc7 sha256 sha512 sha1等。为了校验,需要用do_verify参数。如果是写,那么do_verify=1就意味着写完再读校验,这种会很占内存,因为FIO会把每个数据块的校验数据保存在内存里。do_verify=0时只写校验数据,不做读校验。

读的时候do_verify=1,那么读出来的数据都会做校验值检查,do_verify=0的话,只读数据,不做检查。

另外,verify=meta时,fio会在数据块内写入时间戳、逻辑地址等,此时还能verify_pattern指定写入数据pattern。

FIO其他功能

FIO功能非常强大,可以通过man来查看每一个功能,也有网页版帮助文档https://linux.die.net/man/1/fio。

FIO配置文件

前面的例子都是用命令行来测试,其实也可以用配置文件把这些参数写进去。比如新建FIO配置文件test.log内容如下:

 

 


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17



[global]

filename=/dev/sdc

direct=1

iodepth=64

thread

rw=randread

ioengine=libaio

bs=4k

numjobs=1

size=10G

[job1]

name=job1

offset=0

[job2]

name=job2

offset=10G

;--endjob file


 

保存后,只需要fio test.log就能执行测试任务了。是不是很方便?