为什么O_DIRECT比正常读取慢?

Why O_DIRECT is slower than normal read?

提问人:1f604 提问时间:6/22/2023 最后编辑:1f604 更新时间:7/1/2023 访问量:249

问:

这是我正在使用的代码:

#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <malloc.h>

int main (int argc, char* argv[]) {
    int fd;
    int alignment = 4096;
    int bufsize = 4096 * 4096;
    char* buf = (char*) memalign(alignment, bufsize);
    int i, n, result=0;
    const char* fname = "1GB.txt";

    if ((fd = open(fname, O_RDONLY|O_DIRECT)) < 0) {
        printf("%s: cannot open %s\n", fname);
        exit(2);
    }

    while ( (n = read(fd,buf,bufsize)) > 0 )
        for (i=0; i<n; ++i)
            result += buf[i];
    
    printf("Result: %d\n", result);

    return 0;
}

这是我正在运行的命令:

echo 1 > /proc/sys/vm/drop_caches
time ./a.out 1GB.txt

如果没有O_DIRECT,刷新页面缓存后只需 1.1 秒,使用 O_DIRECT 则需要 2.5 秒。

我尝试更改对齐方式和 bufsize。增加 bufsize 以将运行时间减少到 1.79 秒。增加 bufsize 以将运行时间缩短至 1.75 秒。将对齐减少到 512 将运行时间减少到 1.72 秒。我不知道还能尝试什么。4096 * 4096 * 44096 * 4096 * 64

我不明白为什么使用 O_DIRECT 会使代码变慢。可能是因为我使用的是磁盘加密吗?

我在 Debian 12 内核 6.1.0-9-amd64 上

更新:跟进:为什么即使有预读功能,O_DIRECT也比普通 read() 慢?

C Linux IO

评论

1赞 12431234123412341234123 6/23/2023
不确定,但我假设,当未设置时,内核会在您处理数据时提前读取。如果设置了它,内核将等待调用,读取字节,停止读取并返回系统调用。如果未设置,内核不需要停止读取,并且可以在需要时填满缓存,因此当您进行下一次系统调用时,部分读取已经完成。如果有足够的内存可用,内核可能会使用它进行此类优化。你可以用其他应用程序填满你的RAM来测试这一点。O_DIRECTbufsizeread
1赞 12431234123412341234123 6/23/2023
您是否尝试过 -ing 文件?也许这可以提高速度(也许不是,而且要慢得多,但你必须测试它)。mmap()
0赞 12431234123412341234123 6/23/2023
如果要使用,可以使用共享内存启动第二个进程,其中包含 2 个缓冲区。一个进程 (A) 填满缓冲区 0,通知另一个进程 (B) 并填满缓冲区 1,而进程 B 正在处理缓冲区 0 中的数据,当进程 B 使用缓冲区 0 完成时,它会通知进程 A。当进程 A 完成缓冲区 1 的填充时,它会通知进程 B 并等待进程 B 完成缓冲区 0(如果尚未完成),然后填充缓冲区 0 ....(希望你明白了)。O_DIRECT
0赞 1f604 6/23/2023
嗨,该版本(带和不带 O_DIRECT)大约需要 1.2-1.3 秒,而 read() 大约需要 1.1 秒。请注意,当文件在页面缓存中时,速度会更快,但当文件不在页面缓存中时,速度会更慢。mmap()mmap
2赞 John Bollinger 6/23/2023
请注意,手册中说“一般来说,这会降低性能”,因此您确实观察到这一点也就不足为奇了。O_DIRECT

答:

4赞 Marco Bonelli 6/23/2023 #1

我认为 Linus 在这个旧的邮件列表线程中总结得很好,有人遇到了与你相同的问题:O_DIRECT

2002 年 5 月 10 日星期五,林肯·戴尔写道:

因此,O_DIRECT 2.4.18 中仍然显示为 55% 的性能命中率,而不是 No O_DIRECT。有人有什么线索吗?

是的。

O_DIRECT没有提前阅读。

要O_DIRECT获胜,您需要使其异步。

一直让我感到不安的是O_DIRECT 界面很愚蠢,很可能是一只疯狂的猴子设计的 关于一些严重的精神控制物质[*]。

它根本不漂亮,而且性能也不是很好 因为接口不好(读/写的同步性是其中的一部分 但是,固有的页表行走是另一个问题)。

我敢打赌,你可以通过拆分 实际的 IO 生成和“用户空间映射”是理智的。

因此,您遇到的读取操作速度较慢,因为没有执行预读或缓存,这是没有 的正常行为。O_DIRECT

除非你想请求读取更大的大小,否则如果你进行分块读取,你只有在实现异步操作(例如使用 io_uring)时才能真正受益。Linus 还在上面链接的邮件列表线程中提出了其他有趣的解决方案。O_DIRECT

评论

0赞 Andrew Henle 6/23/2023
莱纳斯在这里很离基地很远。“要想O_DIRECT胜利,你需要让它异步。” 是 bullhockey,读取大块,甚至可能使用多个线程,所以预读无关紧要,跳过页面缓存会提高性能。只要您只读取一次数据。或者您正在读取太多数据,无论如何,任何重新读取都会从缓存中刷新。“疯狂的猴子”是 Linux 实现——即使是简单的调用,也会因为实现而从根本上中断。在 Solaris 上,直接 IO 的速度提高了 20-30%,并且实施得很合理。pwrite()
1赞 Marco Bonelli 6/23/2023
IDK,老实说,我认为 4MiB 缓冲区中等大小。但是,是的,Linux 实现绝对是一个大问题
0赞 Andrew Henle 6/23/2023
同意。在注意到 OP 正在使用未知类型的磁盘加密之前,我发布了该评论。我强烈怀疑这可能会通过为每次调用增加显着的延迟来产生影响,这与禁用预读相结合会减慢速度。read()O_DIRECT
0赞 Marco Bonelli 6/23/2023
实际上 OP 的缓冲区是 16MiB (4096 * 4096),我不擅长数学。
1赞 1f604 7/19/2023
嘿,@MarcoBonelli,只是想说谢谢你提到io_uring。我现在正在尝试,我获得了非常令人难以置信的速度提升。谢谢。