使用IOR的第一步

这是一个简短的教程,介绍IOR的基本用法,以及一些关于如何使用IOR处理缓存效果的技巧,因为这些效果很可能影响您的测量。

运行IOR

运行IOR有两种方式:

  1. 带参数的命令行-可执行文件后面跟着命令行选项。

    $ ./ior -w -r -o filename
    

    这将对文件filename执行写入和读取操作。

  2. 带有脚本的命令行—命令行上的任何参数都将为测试运行建立默认值,但是在执行代码期间,可以将脚本与此结合使用,以进行不同的特定测试。只使用脚本之前的参数!

    $ ./ior -W -f script
    

    默认script中的所有测试都使用写数据检查。

在本教程中,我们将首先使用第一个选项,因为它更容易进行试验并了解IOR。第二个选项虽然更为有用,可以将基准设置保存下来以便稍后重演或测试多种不同的情况。

IOR入门

IOR使用以下参数顺序写入数据:

  • blockSize (-b)
  • transferSize (-t)
  • segmentCount (-s)
  • numTasks (-n)

最好用图表来说明:

../_images/tutorial-ior-io-pattern.png

这四个参数是开始使用IOR所需的全部参数。然而,天真地运行IOR通常会得到令人失望的结果。例如,如果我们运行一个四节点IOR测试,总共写入16 GiB:

$ mpirun -n 64 ./ior -t 1m -b 16m -s 16
...
access bw(MiB/s) block(KiB) xfer(KiB) open(s)  wr/rd(s) close(s) total(s) iter
------ --------- ---------- --------- -------- -------- -------- -------- ----
write  427.36    16384      1024.00   0.107961 38.34    32.48    38.34    2
read   239.08    16384      1024.00   0.005789 68.53    65.53    68.53    2
remove -         -          -         -        -        -        0.534400 2

我们只能从Lustre文件系统中获得每秒几百兆字节的数据,而这个文件系统应该能够提供更多的数据。

使用-FfilePerProcess=1)选项将写入单个共享文件切换到每个进程一个文件,会显著改变性能:

$ mpirun -n 64 ./ior -t 1m -b 16m -s 16 -F
...
access bw(MiB/s) block(KiB) xfer(KiB) open(s)  wr/rd(s) close(s) total(s) iter
------ --------- ---------- --------- -------- -------- -------- -------- ----
write  33645     16384      1024.00   0.007693 0.486249 0.195494 0.486972 1
read   149473    16384      1024.00   0.004936 0.108627 0.016479 0.109612 1
remove -         -          -         -        -        -        6.08     1

这在很大程度上是因为让每个MPI进程在自己的文件上工作可以消除由于文件锁定而产生的任何争用。

然而,我们的初始测试和每个进程的文件测试之间的性能差异有点极端。事实上,在Lustre上实现146 GB/s读取速率的唯一方法是,四个计算节点中的每一个都有超过45 GB/s的Lustre网络带宽,也就是说,每个计算和存储节点上都有400 Gbit的链路。

页缓存对基准测试的影响

真正发生的是IOR读取的数据实际上并不是来自Lustre;相反,文件的内容已经被缓存,IOR能够直接从每个计算节点的DRAM中读取它们。由于Linux(和Lustre)使用回写缓存来缓冲I/O,数据在IOR的写入阶段被缓存,因此IOR并不是直接向Lustre写入和读取数据,而是实际上主要与每个计算节点上的内存进行通信。

更具体地说,尽管每个IOR进程认为它正在写入Lustre上的文件,然后从Lustre回读该文件的内容,但实际上是这样

  1. 将数据写入缓存在内存中的文件副本。如果在写入之前内存中没有缓存的文件副本,则首先将被修改的部分加载到内存中。
  2. 内存中与Lustre上不同的文件部分(称为“页面”)被标记为“脏”
  3. write()调用完成,IOR继续,即使写入的数据仍未提交给Lustre
  4. 独立于IOR,操作系统内核会不断扫描文件缓存,查找内存中已更新但未在Lustre上更新的文件(“脏页”),然后将缓存的修改提交给Lustre
  5. 脏页被声明为非脏页,因为它们现在与硬盘上的内容同步,但它们仍保留在内存中

然后,当IOR的读阶段紧跟着写阶段时,IOR能够从内存中检索文件的内容,而不必通过网络与Lustre通信。

有几种方法可以衡量底层Lustre文件系统的读取性能。最粗糙的方法是简单地写入超过总页面缓存容量的数据,这样在写入阶段完成时,文件的开头已经从缓存中删除。例如,增加段(-s)的数量以写入更多数据,可以非常清楚地显示测试系统上节点页面缓存的运行点:

../_images/tutorial-ior-overflowing-cache.png

但是,这可能会使在具有大量节点内内存的系统上运行IOR花费很长时间。

更好的选择是让每个节点上的MPI进程只读取它们不写入的数据。例如,在每节点四个进程的测试中,将MPI进程的映射移到四个块,使每个节点N读取节点N-1写入的数据。

../_images/tutorial-ior-reorderTasks.png

由于页缓存不会在计算节点之间共享,因此以这种方式转移任务可以确保每个MPI进程都在读取它没有写入的数据。

IOR提供了-C选项(reorderTasks)来执行此操作,并且它强制每个MPI进程读取其相邻节点写入的数据。使用此选项运行IOR可以提供更可靠的读取性能:

$ mpirun -n 64 ./ior -t 1m -b 16m -s 16 -F -C
...
access bw(MiB/s) block(KiB) xfer(KiB) open(s)  wr/rd(s) close(s) total(s) iter
------ --------- ---------- --------- -------- -------- -------- -------- ----
write  41326     16384      1024.00   0.005756 0.395859 0.095360 0.396453 0
read   3310.00   16384      1024.00   0.011786 4.95     4.20     4.95     1
remove -         -          -         -        -        -        0.237291 1

但是现在看起来应该很明显,写性能也高得离谱。同样,这是由于页面缓存,当写入已提交到内存而不是底层Lustre文件系统时,它会向IOR发出写操作完成的信号。

为了解决页面缓存对写入性能的影响,我们可以在所有write()返回后立即发出fsync()调用,以强制我们刚刚写入的脏页面清除到Lustre。包括fsync90完成所需的时间,可以让我们衡量数据写入页面缓存和页面缓存写回Lustre所需要的时间。

IOR提供了另一个方便的选项-efsync)来完成此操作。再一次,使用这个选项改变了我们的性能测量:

$ mpirun -n 64 ./ior -t 1m -b 16m -s 16 -F -C -e
...
access bw(MiB/s) block(KiB) xfer(KiB) open(s)  wr/rd(s) close(s) total(s) iter
------ --------- ---------- --------- -------- -------- -------- -------- ----
write  2937.89   16384      1024.00   0.011841 5.56     4.93     5.58     0
read   2712.55   16384      1024.00   0.005214 6.04     5.08     6.04     3
remove -         -          -         -        -        -        0.037706 0

最后,我们对文件系统有了一个可信的带宽测量。

破坏页缓存

由于IOR是专门为I/O基准测试而设计的,因此它提供了这些选项,使您尽可能轻松地确保实际测量的是文件系统的性能,而不是计算节点的内存。话虽这么说,它生成的I/O模式旨在展示峰值性能,而不是反映实际应用程序可能要做的事情,因此,在很多情况下,使用IOR测量I/O性能并不总是最佳选择。有几种方法可以让我们变得更聪明,并在更一般的意义上破坏页缓存,从而获得有意义的性能数字。

When measuring write performance, bypassing page cache is actually quite simple; opening a file with the O_DIRECT flag going directly to disk. In addition, the fsync() call can be inserted into applications, as is done with IOR’s -e option.

在测量写入性能时,绕过页缓存实际上非常简单;打开一个带有O_DIRECT标志的文件,直接进入硬盘。此外,fsync()调用可以插入到应用程序中,就像IOR的-e选项一样。

测量读取性能要棘手得多。如果你有幸在测试系统上拥有root访问权限,可以通过以下操作强制Linux内核清空其页缓存

# echo 1 > /proc/sys/vm/drop_caches

事实上,在运行任何基准测试(例如,Linpack)之前,这通常是一种很好的做法,因为它可以确保在基准测试应用程序开始为自己分配内存时,不会因为内核试图驱逐页面而损失性能。

不幸的是,许多人在我们的系统上没有root,所以我们必须变得更加聪明。事实证明,有一种方法可以向内核传递一个提示,即页缓存中不再需要文件:

#define _XOPEN_SOURCE 600
#include <unistd.h>
#include <fcntl.h>
int main(int argc, char *argv[]) {
    int fd;
    fd = open(argv[1], O_RDONLY);
    fdatasync(fd);
    posix_fadvise(fd, 0,0,POSIX_FADV_DONTNEED);
    close(fd);
    return 0;
}

在Linux中,使用POSIX_FADV_DONTNEED()传递POSIX_FADV_DONTNEED的效果通常是将属于该文件的所有页面从页缓存中清除。然而,这只是一个提示—而不是保证—内核会异步地退出这些页面,因此页面可能需要一到两秒钟才能真正离开页面缓存。幸运的是,Linux还提供了一种方法来探测文件中的页面,以查看它们是否驻留在内存中。

最后,通常最简单的方法是限制可用于页缓存的内存量。因为应用程序内存总是优先于缓存内存,所以简单地在节点上分配大部分内存将强制清除大部分缓存页面。IOR的新版本提供了memoryPerNode选项,可以做到这一点,效果是人们所期望的:

../_images/tutorial-ior-memPerNode-test.png

上图显示了从总DRAM为128 GiB的单个节点测量的带宽。每个x标签上的第一个百分比是基准作为应用程序内存保留的128 GiB量,第二个百分比是总写入量。例如,“50%/150%”数据点对应于为应用程序分配的50%的节点内存(64GiB),总共读取192GiB的数据。

此基准测试在单个旋转硬盘上运行,该硬盘的速度不超过130 MB/秒,因此显示性能高于此的条件受益于缓存提供的某些页面。考虑到异常高的性能测量是在相对于读取的数据量有足够的内存缓存时获得的,这是完全有道理的。

推论

在很大程度上,由于页缓存的影响,衡量I/O性能比CPU性能有点棘手。也就是说,页缓存的存在是有原因的,在许多情况下,应用程序的I/O性能最好由大量使用缓存的基准来表示。

例如,BLAST生物信息学应用程序将其所有输入数据重新读两次;第一次初始化数据结构,第二次填充它们。因为第一次读取会缓存每个页面,并允许第二次读从缓存中出来,而不是从文件系统中出来,所以在禁用页面缓存的情况下运行此I/O模式会导致速度减慢约2倍:

../_images/tutorial-cache-vs-nocache.png

因此,让页缓存完成它的工作通常是用现实的应用程序I/O模式进行基准测试的最现实的方法。一旦你知道页缓存可能会如何影响你的测量,你就很有可能推断出最有意义的性能指标是什么。

posted @ 2024-11-04 16:36  LoftyAmbition  阅读(88)  评论(0编辑  收藏  举报