页面缓存缓存从硬盘驱动器缓存最近访问的页面,从而减少后续访问相同数据的查找时间。首次从硬盘访问页面时,页面高速缓存不会提高性能。因此,如果应用程序要读取一次文件,并且只读取一次,那么绕过页面缓存是更好的方法。这可以通过使用 O_DIRECT 标记来实现。这意味着内核不会将此特定数据视为页面缓存。减少缓存争用意味着其他页面(将重复访问)更有可能保留在页面缓存中。这样可以提高缓存命中率,从而提供更好的性能。
void ioReadOnceFile()
{
/* 使用 direct_fd 和 direct_f 会绕过内核 page-cache。
*- direct_fd 是一个低级文件描述符
* - direct_f 是一个类似于 fopen()
* 返回的文件流 注意:使用 getpagesize() 确定最佳大小的缓冲区。
*/
int direct_fd = open(“文件名”, O_DIRECT |O_RDWR);
文件 *direct_f = fdopen(direct_fd, “w+”);
/* 直接磁盘 I/O 在此处完成*/
关闭(f);
关闭 (fd);
}
考虑在由大量页面组成的大文件(即数据库)中进行读取的情况。访问的每个后续页面都会进入页面缓存,但随着读取的页面越来越多,这些页面会被丢弃。这会严重降低缓存命中率。在这种情况下,页面高速缓存不会提供任何性能增益。因此,在访问大文件时最好绕过页面缓存。
void ioLargeFile()
{
/* 使用 direct_fd 和 direct_f 会绕过内核 page-cache。
*- direct_fd 是一个低级文件描述符
* - direct_f 是一个类似于 fopen()
* 返回的文件流 注意:使用 getpagesize() 确定最佳大小的缓冲区。
*/
int direct_fd = open(“largefile.bin”, O_DIRECT |O_RDWR |O_LARGEFILE);
文件 *direct_f = fdopen(direct_fd, “w+”);
/* 直接磁盘 I/O 在此处完成*/
关闭(f);
关闭 (fd);
}
io-scheduler 可优化要排队到硬盘的 I/O 操作的顺序。由于搜索时间是硬盘驱动器上最重的惩罚,因此大多数 I/O 调度程序会尝试最大限度缩短搜索时间。这是作为电梯算法的变体实现的,即将来自众多进程的随机排序请求重新排序为数据在硬盘上存在的顺序,这需要大量的 CPU 时间。
某些涉及复杂操作的任务往往受到 CPU 处理大量数据的速度的限制。在后台运行的复杂 I/O 调度程序可能会占用宝贵的 CPU 周期,从而降低系统性能。在这种情况下,切换到更简单的算法(如空运算)可以减少 CPU 负载并提高系统性能。
echo noop > /sys/block//queue/scheduler
虽然这最终会完成工作,但这绝对不是最佳方法。从内核的角度来看,I/O 请求的最佳大小是文件系统块大小(即页面大小)。由于文件系统(以及内核页面缓存)中的所有 I/O 都是以页面为单位的,因此应用程序以页面大小的倍数进行传输也是有意义的。此外,随着多段高速缓存现在进入硬盘驱动器,以数倍的数据块大小执行 I/O 将受益匪浅。
以下命令可用于确定最佳数据块大小
统计信息 --printf=“bs=%s optimal-bs=%S\n” --file-system /dev/
当应用程序启动 SYNC I/O 读取时,内核会对数据的读取操作进行排队,并且仅在读回整个请求数据块后才返回。在此期间,内核会将应用程序的进程标记为因 I/O 而被阻止。其他进程可以利用 CPU,从而提高系统的整体性能。
当应用程序启动 SYNC I/O 写入时,内核会对数据的写入操作进行排队,从而将应用程序的进程置于阻塞的 I/O 中。不幸的是,这意味着当前应用程序的进程被阻止,并且无法执行任何其他处理(或 I/O),直到此写入操作完成。
当应用启动异步 I/O 读取时,read() 函数通常在读取大型数据块的子集后返回。应用需要重复调用 read() 来处理剩余要读取的数据大小,直到读入所需的全部数据。每次额外的读取调用都会引入一些开销,因为它在用户空间和内核之间引入了上下文切换。实现一个紧密循环来重复调用 read() 会浪费其他进程本可以使用的 CPU 周期。因此,通常使用 select() 实现阻塞,直到下一个 read() 返回非零字节读入。也就是说,ASYNC 被设置为阻塞,就像 SYNC 读取一样。
当应用启动异步 I/O 写入时,内核会更新页面缓存中的相应页面并将其标记为脏页面。然后,控件快速返回到可以继续运行的应用。数据稍后会在更优化的时间(较低的 CPU 负载)以更优化的方式(按顺序写入)刷新到硬盘。
因此, SYNC 读取 和 ASYNC 写入 通常是一个不错的方法,因为它们允许内核优化底层 I/O 请求的顺序和时间。