通过预读优化读取 I/O,同时避免将数据存储在页缓存中

通过预读优化读取 I/O,同时避免将数据存储在页缓存中

我需要能够从文件中顺序读取数据,同时不将正在读取的数据存储在页面缓存中,因为预计不会再次读取文件内容,而且盒子上存在内存压力(想要将宝贵的内存用于有用的磁盘 I/O 缓存)。

我的问题是如何优化这些读取。因为我知道正在读取的数据是按顺序放置在磁盘上(减去碎片),所以我希望能够提前读取(通过增加 /sys/block/sda/queue/read_ahead_kb ),但我不确定这是否会带来任何好处,因为我必须使用 posix_fadvise (带有 POSIX_FADV_DONTNEED 标志)来防止正在读取的数据存储在页面缓存中。

预读数据是否会因为提示从页面缓存中删除数据而被简单地丢弃?

答案1

使用直接IO

直接 I/O 是文件系统的一项功能,文件读取和写入直接从应用程序到存储设备,绕过操作系统读取和写入缓存。直接 I/O 仅由管理自己的缓存的应用程序(例如数据库)使用。

应用程序通过打开带有该标志的文件来调用直接 I/O O_DIRECT

例如:

int fd = open( filename, O_RDONLY | O_DIRECT );

Linux 上的直接 IO 很奇怪并且有一些限制。应用程序IO缓冲区必须是页面对齐的,并且某些文件系统要求每个IO请求是页面大小的精确倍数。最后一个限制可能会使读/写文件的最后部分变得困难。

在应用程序中处理预读的一种易于编码的方法可以使用fdopen和 设置一个大的页面对齐缓冲区来完成posix_memalignsetvbuf

// should really get page size using sysconf()
// but beware of systems with multiple page sizes
#define ALIGNMENT ( 4UL * 1024UL )
#define BUFSIZE ( 1024UL * 1024UL )
char *buffer;
...

int fd = open( filename, O_RDONLY | O_DIRECT );
FILE *file = fdopen( fd, "rb" );

int rc = posix_memalign( &buffer, ALIGNMENT, BUFSIZE );
rc = setvbuf( file, buffer, _IOFBF, BUFSIZE );

您还可以用来mmap()获取匿名内存以用于缓冲区。这样做的优点是自然页面对齐:

...
char *buffer = mmap( NULL, BUFSIZE, PROT_READ | PROT_WRITE,
    MAP_ANONYMOUS | MAP_PRIVATE, -1, 0 );
rc = setvbuf( file, buffer, _IOFBF, BUFSIZE );

然后只需使用fread()/fgets()或任何FILE *您想要从file流中读取的类型的读取函数。

您确实需要使用工具进行检查,例如strace实际的read系统调用是否是通过页面对齐和页面大小的缓冲区完成的 - 基于流处理的某些 C 库实现不使用仅用于 IO 缓冲FILE *指定的缓冲区,setvbuf因此对齐方式和尺寸可能会出现偏差。我不认为 Linux/glibc 会这样做,但如果你不检查并且大小和/或对齐关闭,你的 IO 调用将会失败。

再说一遍 - Linux 直接 IO 可能很奇怪。只有某些文件系统支持直接 IO,并且其中一些文件系统比其他文件系统更加特殊。 测试如果您决定使用它,请彻底执行此操作。

每当流的缓冲区需要填充时,发布的代码将执行 1 MB 的预读。您还可以使用线程实现更复杂的预读 - 一个线程填充一个缓冲区,其他线程从已满的缓冲区中读取。这将避免在预读完成时处理“断断续续”,但代价是大量相对复杂的多线程代码。

答案2

这个问题回答得很好在这个 StackOverflow 问题中

  • 跟踪之前的文件偏移量read()

  • 之后read(),调用fadvise(POSIX_FADV_DONTNEED)数据范围[...]

  • 为了获得最佳结果,您必须读取页对齐块中的数据。 I/O 缓存是基于页的,并将fadvise()指定的数据范围映射到页列表中。未对准会导致额外的 read()s(并损害性能),但除此之外是无害的。

你不想读书如果你优化内存。你只是想读书。如果您读取,磁盘将按顺序操作,无需告诉它请流式传输数据。

相关内容