我需要能够从文件中顺序读取数据,同时不将正在读取的数据存储在页面缓存中,因为预计不会再次读取文件内容,而且盒子上存在内存压力(想要将宝贵的内存用于有用的磁盘 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_memalign
:setvbuf
// 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(并损害性能),但除此之外是无害的。
你不想读书先如果你优化内存。你只是想读书。如果您读取,磁盘将按顺序操作,无需告诉它请流式传输数据。