背景
在Windows下有一些磁盘基准测试工具,用于测试硬盘/SD卡的读写速度,如ATTO Disk Benchmark(注:单词「benchmark」就是基准检查的意思)。
上一篇文章「市面常见存储卡的读写速度对比测试」,就是用ATTO Disk Benchmark工具在Windows下测试得到的数据。
在PC上测试到的磁盘读写性能数据,是否适用嵌入式设备呢?SD卡的读写速度,受限制于SD卡本身固有的性能之外,还跟读卡器,主CPU性能等有一定的关系。所以,同一张SD卡,想要知道其在嵌入式设备中实际的读写性能,还需要在嵌入式环境中实际测试下才准确。
ATTO Disk Benchmark工具在测试SD卡读写性能时,会依次以512B、1K、2K、4K……1M、2M等作为一次读写的包大小来进行测试,这样的测试会得知以多大的包去读写SD卡,才能发挥出其性能的极限。以下介绍dd时,不会做这么多的测试,本文的重点是讲解如何使用dd命令来测试磁盘读写速度,至于要像ATTO Disk Benchmark工具这样自动化测试,可以使用shell脚本调用dd命令去实现。
dd使用指定的输入和输出块大小来拷贝文件,它每次从输入读取指定大小的一个块写到独立的输出块去,通过这种方法来测试读写速度。
测试环境
- 硬件:嵌入式ARM
-
系统:
# cat /proc/version
Linux version 3.0.8 (root@Ubuntu10) (gcc version 4.4.1 (Hisilicon_v100(gcc4.4-290+uclibc_0.9.32.1+eabi+linuxpthread)) ) #252 Mon Aug 17 11:33:23 CST 2015 内存:512M
- SD卡:如果没有特殊说明,本文都是使用以下SD卡进行测试,其class 4理论标称写速度为4M/s,如下图所示,同时附上在PC上使用ATTO Disk Benchmark工具的测试结果图:
内核缓存
在开始测试之前,必须先弄明白“内核缓存”的概念,否则很容易测出错误的数据。有关“内核缓存”的详细概念,可参考「附录一 linux内核缓存」。以下摘选重点:
向文件中写入数据时,数据会先缓存在Page Cache中,内存中的这部分数据被标注为Dirty Page,linux系统上的pdflush守护进程会跟进系统设置将将这部分Dirty Page刷到磁盘上,也可以通过fsync系统调用在数据写入后强制刷到磁盘上。将写入的数据刷入磁盘时,是以Buffer Cache为单位,每次回写若干个Buffer Cache。
读取文件内容时,系统会一次性连续读取包括所请求页面在内的多个页面(如预读页面个数为n)。如果请求的页面在page cache中命中的话,会从缓存中返回页面内容,增加读取的页面数量,异步读取2n个页面;如果请求的页面没有在page cache中命中,也会增加读取页面数量,同步读取2n个页面。
为了避免内核缓存对测试带来影响,在每条测试语句之前,都要先清除linux内核缓存,命令如下(详情可参考「附录二 如何清除linux内核缓存」):
# sync; echo 3 > /proc/sys/vm/drop_caches
测试磁盘写能力
正确的命令
# sync; echo 3 > /proc/sys/vm/drop_caches
#
# dd bs=1M count=500 if=/dev/zero of=/mnt/sdisk/ftp/outfile
500+0 records in
500+0 records out
524288000 bytes (500.0MB) copied, 122.160615 seconds, 4.1MB/s
#
# sync; echo 3 > /proc/sys/vm/drop_caches
#
# dd bs=20k count=25600 if=/dev/zero of=/mnt/sdisk/ftp/outfile
25600+0 records in
25600+0 records out
524288000 bytes (500.0MB) copied, 123.208135 seconds, 4.1MB/s
#
因为/dev/zero是一个伪设备,它只产生空字符流,对它不会产生IO,所以,IO都会集中在of文件中,of文件只用于写,所以这个命令相当于测试磁盘的写能力。
以上dd命令结束的时候,数据其实还没有真正写到磁盘上去,而是写到内核缓存就返回了,究竟有多少数据写入磁盘了是由内核控制的。有两种方法可以让内核缓存数据写入磁盘:
方法一:使用conv=fsync选项,明确要求dd命令每写一次数据,都强制将内核缓存写入磁盘中。如果count设置很大的值(N),会触发N次的强制写入磁盘,这不符合实际的使用情况,除非像数据库插入这种写动作,否则一般的应用程序都不会频繁要求系统缓存数据强制刷入磁盘,这样反而不能发挥出写速度的最佳性能,而是让内核自己控制。
方法二:写入的数据量要足够大,越大越好,超过内存值为佳,让内核缓存放不小,间接的逼迫内核将数据写到磁盘中,以此测试出更接近实际的速度值,越大越能测出接近真实的速度值。这种方法dd命令返回,虽然有可能还是有数据残留在内核缓存中未写入磁盘,但只要写入的数据足够大,这点缓存带来的影响是可以忽略不计的,以上测试就是采用本方法。
错误的命令
# sync; echo 3 > /proc/sys/vm/drop_caches
# dd bs=20M count=1 if=/dev/zero of=/mnt/sdisk/ftp/outfile
20+0 records in
20+0 records out
20971520 bytes (20.0MB) copied, 0.258525 seconds, 77.4MB/s
#
77.4M/s,严重超出SD卡的标称值,肯定不可信。以上dd命令结束的时候,数据其实还没有真正写到磁盘上去,而是写到内核缓存就返回了,由于数据量太小,很快就写入内核缓存了,都没有触发写入磁盘,当然速度快到离谱。
# sync; echo 3 > /proc/sys/vm/drop_caches
# dd bs=20M count=1 conv=fsync if=/dev/zero of=/mnt/sdisk/ftp/outfile
1+0 records in
1+0 records out
20971520 bytes (20.0MB) copied, 5.810785 seconds, 3.4MB/s
#
加上conv=fsync选项,强制dd命令每次写数据都写入磁盘,如此数据会更真实,但conv=fsync和count搭配是有陷阱的,往下看:
# sync; echo 3 > /proc/sys/vm/drop_caches
#
# dd bs=40k count=512 conv=fsync if=/dev/zero of=/mnt/sdisk/ftp/outfile
512+0 records in
512+0 records out
20971520 bytes (20.0MB) copied, 7.742188 seconds, 2.6MB/s
#
# sync; echo 3 > /proc/sys/vm/drop_caches
#
# dd bs=20k count=1024 conv=fsync if=/dev/zero of=/mnt/sdisk/ftp/outfile
1024+0 records in
1024+0 records out
20971520 bytes (20.0MB) copied, 13.830230 seconds, 1.4MB/s
#
# sync; echo 3 > /proc/sys/vm/drop_caches
#
# dd bs=10k count=2048 conv=fsync if=/dev/zero of=/mnt/sdisk/ftp/outfile
2048+0 records in
2048+0 records out
20971520 bytes (20.0MB) copied, 21.752566 seconds, 941.5KB/s
#
同样是写入20M数据,使用了conv=fsync选项,但count设置为N多次,这样会触发N次的强制写入磁盘(每次写入的数据bs都很小),如此也会降低写入速度。这种方式也不符合实际情况。
似是而非的命令
conv=fsync选项搭配count=1,同时bs设置很大,让dd命令返回前保证数据已经被刷入磁盘,岂不是更能测试SD卡的写速度值。如:
# sync; echo 3 > /proc/sys/vm/drop_caches
#
# dd bs=100M count=1 conv=fsync if=/dev/zero of=/mnt/sdisk/ftp/outfile
1+0 records in
1+0 records out
104857600 bytes (100.0MB) copied, 25.407060 seconds, 3.9MB/s
#
理论上这么说是对的,但仔细想想,不太对劲。第一,应用程序(或者内核缓存),数据不会缓存到那么大(如上100M)才触发写一次,这样丢失数据的风险太大。第二受限制于物理内存大小,bs能设置的上限值有限,超过可用的内存大小就会报错,如下所示:
# sync; echo 3 > /proc/sys/vm/drop_caches
#
# dd bs=500M count=1 conv=fsync if=/dev/zero of=/mnt/sdisk/ftp/outfile
dd: memory exhausted
#
测试磁盘读能力
正确的命令
# sync; echo 3 > /proc/sys/vm/drop_caches
#
# dd bs=20k count=4096 if=/dev/sda1 of=/dev/null
4096+0 records in
4096+0 records out
83886080 bytes (80.0MB) copied, 5.119588 seconds, 15.6MB/s
#
因为/dev/sdb1是一个物理分区,对它的读取会产生IO,/dev/null是伪设备,相当于黑洞,of到该设备不会产生IO,所以,这个命令的IO只发生在/dev/sdb1上,也相当于测试磁盘的读能力。
错误的命令
不清理内核缓存,对同一个文件连续进行多次读速度测试,如下所示:
# dd bs=20k count=4096 if=/dev/sda1 of=/dev/null
4096+0 records in
4096+0 records out
83886080 bytes (80.0MB) copied, 5.119588 seconds, 15.6MB/s
#
# dd bs=20k count=4096 if=/dev/sda1 of=/dev/null
4096+0 records in
4096+0 records out
83886080 bytes (80.0MB) copied, 0.307665 seconds, 260.0MB/s
#
# dd bs=20k count=4096 if=/dev/sda1 of=/dev/null
4096+0 records in
4096+0 records out
83886080 bytes (80.0MB) copied, 0.290451 seconds, 275.4MB/s
#
# dd bs=20k count=4096 if=/dev/sda1 of=/dev/null
4096+0 records in
4096+0 records out
83886080 bytes (80.0MB) copied, 0.237559 seconds, 336.8MB/s
#
你会发现,对同一个文件(在linux中“一切皆文件”,设备也是文件)进行多次读速度测试,第一次读速度是15MB/s,第二次之后竟然都飙升到260MB/s,远远超出SD卡的标称值,不科学啊。这其实是linux内核缓存I/O中的“文件Cache”在作祟,什么是文件Cache可参考「附录一 linux内核缓存」。
我对这种现象简单粗暴的理解:第一次读文件,是从磁盘卡中读取,是真实的读速度,读完之后该文件被内核缓存在内存中,第二次读时,只是从内存中获取文件数据(专业名词是cache命中),并没有真正的从磁盘读,所以速度贼快。
非要对同一文件进行多次的读测试,我们需要先清除linux内核的缓存,可以使用如下命令
# sync; echo 3 > /proc/sys/vm/drop_caches
来清除内核缓存,关于该命令的详细说明,可参考「附录二 如何清除linux内核缓存」。最后测试结果如下所示:
# dd bs=20k count=4096 if=/dev/sda1 of=/dev/null
4096+0 records in
4096+0 records out
83886080 bytes (80.0MB) copied, 5.136499 seconds, 15.6MB/s
#
# sync; echo 3 > /proc/sys/vm/drop_caches
#
# dd bs=20k count=4096 if=/dev/sda1 of=/dev/null
4096+0 records in
4096+0 records out
83886080 bytes (80.0MB) copied, 5.189734 seconds, 15.4MB/s
#
测试同时读写能力
# sync; echo 3 > /proc/sys/vm/drop_caches
#
# dd bs=20k count=25600 if=/dev/sda1 of=/mnt/sdisk/ftp/outfile
25600+0 records in
25600+0 records out
524288000 bytes (500.0MB) copied, 151.556191 seconds, 3.3MB/s
#
这个命令下,一个是物理分区,一个是实际的文件,对它们的读写都会产生IO(对/dev/sdb1是读,对mnt/sdisk/ftp/outfile写),假设他们都在一个磁盘中,这个命令就相当于测试磁盘的同时读写能力。
同样的,同一命令连续多次测试时,要记得清除内核缓存。
附录一 linux内核缓存
什么是缓存 I/O (Buffered I/O)
缓存 I/O 又被称作标准 I/O,大多数文件系统的默认 I/O 操作都是缓存 I/O。在 Linux 的缓存 I/O 机制中,操作系统会将 I/O 的数据缓存在文件系统的页缓存( page cache )中,也就是说,数据会先被拷贝到操作系统内核的缓冲区中,然后才会从操作系统内核的缓冲区拷贝到应用程序的地址空间。缓存 I/O 有以下这些优点:
缓存 I/O 使用了操作系统内核缓冲区,在一定程度上分离了应用程序空间和实际的物理设备。
缓存 I/O 可以减少读盘的次数,从而提高性能。
当应用程序尝试读取某块数据的时候,如果这块数据已经存放在了页缓存中,那么这块数据就可以立即返回给应用程序,而不需要经过实际的物理读盘操作。当然,如果数据在应用程序读取之前并未被存放在页缓存中,那么就需要先将数据从磁盘读到页缓存中去。对于写操作来说,应用程序也会将数据先写到页缓存中去,数据是否被立即写到磁盘上去取决于应用程序所采用的写操作机制:如果用户采用的是同步写机制( synchronous writes ), 那么数据会立即被写回到磁盘上,应用程序会一直等到数据被写完为止;如果用户采用的是延迟写机制( deferred writes ),那么应用程序就完全不需要等到数据全部被写回到磁盘,数据只要被写到页缓存中去就可以了。在延迟写机制的情况下,操作系统会定期地将放在页缓存中的数据刷到磁盘上。与异步写机制( asynchronous writes )不同的是,延迟写机制在数据完全写到磁盘上的时候不会通知应用程序,而异步写机制在数据完全写到磁盘上的时候是会返回给应用程序的。所以延迟写机制本身是存在数据丢失的风险的,而异步写机制则不会有这方面的担心。
文件 Cache
文件 Cache 是文件数据在内存中的副本,因此文件 Cache 管理与内存管理系统和文件系统都相关:一方面文件 Cache 作为物理内存的一部分,需要参与物理内存的分配回收过程,另一方面文件 Cache 中的数据来源于存储设备上的文件,需要通过文件系统与存储设备进行读写交互。从操作系统的角度考虑,文件 Cache 可以看做是内存管理系统与文件系统之间的联系纽带。因此,文件 Cache 管理是操作系统的一个重要组成部分,它的性能直接影响着文件系统和内存管理系统的性能。
为了提升磁盘设备的IO性能,操作系统会使用内存作为磁盘设备的cache,并使用memory map方式在访问时建立与文件系统的缓存映射。文件系统的缓存,是以Page Cache为单位,一个Page Cache包含多个Buffer Cache。
向文件中写入数据时,数据会先缓存在Page Cache中,内存中的这部分数据被标注为Dirty Page,linux系统上的pdflush守护进程会跟进系统设置将将这部分Dirty Page刷到磁盘上,也可以通过fsync系统调用在数据写入后强制刷到磁盘上。将写入的数据刷入磁盘时,是以Buffer Cache为单位,每次回写若干个Buffer Cache。
读取文件内容时,系统会一次性连续读取包括所请求页面在内的多个页面(如预读页面个数为n)。如果请求的页面在page cache中命中的话,会从缓存中返回页面内容,增加读取的页面数量,异步读取2n个页面;如果请求的页面没有在page cache中命中,也会增加读取页面数量,同步读取2n个页面。
文件 Cache 管理指的就是对这些由操作系统分配,并用来存储文件数据的内存的管理。 Cache 管理的优劣通过两个指标衡量:一是 Cache 命中率,Cache 命中时数据可以直接从内存中获取,不再需要访问低速外设,因而可以显著提高性能;二是有效 Cache 的比率,有效 Cache 是指真正会被访问到的 Cache 项,如果有效 Cache 的比率偏低,则相当部分磁盘带宽会被浪费到读取无用 Cache 上,而且无用 Cache 会间接导致系统内存紧张,最后可能会严重影响性能。
附录二 如何清除linux内核缓存
在 Linux 上如何清除内核缓存?
1.仅清除页面缓存(PageCache)
# sync; echo 1 > /proc/sys/vm/drop_caches
2.清除目录项和inode
# sync; echo 2 > /proc/sys/vm/drop_caches
3.清除页面缓存,目录项和inode
# sync; echo 3 > /proc/sys/vm/drop_caches
上述命令的说明:
sync 将刷新文件系统缓冲区(buffer),命令通过“;”分隔,顺序执行,shell在执行序列中的下一个命令之前会等待命令的终止。正如内核文档中提到的,写入到drop_cache将清空缓存而不会杀死任何应用程序/服务,echo命令做写入文件的工作。
有关drop_caches文件的详细说明,可参考官方文档(以关键词drop_caches搜索).