V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
lzjamao
V2EX  ›  程序员

基于 mmap 相比于 fwrite 写日志,是否有性能优势?

  •  1
     
  •   lzjamao · 2021-07-25 16:02:11 +08:00 · 4330 次点击
    这是一个创建于 1208 天前的主题,其中的信息可能已经有所发展或是发生改变。

    看到一些日志库和文章说,声称基于 mmap 可以增强性能,如腾讯 xloglog4a

    微信 mars 的高性能日志模块 xlog

    log4a readme

    《高性能日志记录方式 - mmap 》

    不过在 stackoverflow 上看到了相反说法(这里),有人认为 mmap 和 fwrite 在顺序写性能上差别不太( 微信 mars 的高性能日志模块 xlog文章里的测试结果也是说相差无几),有人认为不恰当的使用 mmap 会使性能差别。

    27 条回复    2021-07-26 21:06:54 +08:00
    ipwx
        1
    ipwx  
       2021-07-25 16:43:45 +08:00
    mmap 比 fwrite 快的主要原因,不是因为不需要一次内存拷贝么 2333 。fwrite 需要把内容拷贝到内核去,但是 mmap 直接就是内核的。

    然后 mmap 写回磁盘的最小数据单位是 page size,一般 linux 上面是 4096 。好处是 flush / cache 都是 linux 内核管的,和虚拟内存走相同的方式。而且就算进程崩溃了,因为 mmap 是虚拟内存块,所以该写回的还是会写回。那么坏处就很显然了,如果每次都只改某些 4096 块的某些小地方,要写回的东西还是蛮多了(因为写回一次是按 4096 分块的)。读也亦然。
    ipwx
        2
    ipwx  
       2021-07-25 16:46:46 +08:00
    然后楼主你举的那个日志的例子很特殊,是永远顺序往下写的。写满一个 4096 块就不会再动了,系统完全有机会每次写满 4096 再刷新。而且因为是 mmap,每次写一条日志就不需要 flush (因为你不怕内存崩溃),相当于避免了频繁 << 4096 大小的文件写入,自然变快了。

    缺点也很明显了,日志不是完全实时的。
    ipwx
        3
    ipwx  
       2021-07-25 16:47:29 +08:00
    不怕内存崩溃 => 不怕进程崩溃

    话说回来依赖 mmap 如果系统宕机或者断电了,那也就没机会写回磁盘了。这点和 flush 就不一样了
    sagaxu
        4
    sagaxu  
       2021-07-25 16:54:32 +08:00 via Android
    顺序写入时,mmap 不一定比 write 快,内存复制比磁盘 IO 快一个数量级,省掉这一步收效甚微,内核还要维护 mmap 的映射关系,这也是有成本的。我觉得顺序写入日志完全没必要用 mmap,性能差不多,代码更复杂,跨平台性还更差。
    ipwx
        5
    ipwx  
       2021-07-25 16:55:47 +08:00
    @sagaxu 主要是省了 flush 啊。你普通写入为了避免程序崩溃看不到最后的日志,很多时候要频繁 flush 的。
    billlee
        6
    billlee  
       2021-07-25 17:14:06 +08:00
    谁会像微信 xlog 那样写个几个 G 的日志啊
    BiteTheDust
        7
    BiteTheDust  
       2021-07-25 17:44:07 +08:00
    按照自己的经历 在顺序读写几十个 g 的情况下 在外面自己管理了一层缓存 把 fwrite 改成 mmap 似乎没有什么性能提升(甚至有下降)
    ipwx
        8
    ipwx  
       2021-07-25 18:08:55 +08:00
    @BiteTheDust 毕竟 fwrite 到 mmap 还得复制一份。mmap 的正确操作不是直接写入么 2333
    ryd994
        9
    ryd994  
       2021-07-25 19:20:47 +08:00 via Android
    @ipwx 问题是你内存里那一份又是哪来的?
    要记录的数据那肯定是软件运行时本身就用的数据。那为什么不 fprintf 直接格式化进文件呢?
    如果你是怕储存速度跟不上的话,那比起写 mmap 然后指望不知道什么时候会 flush,还不如加大文件 write buffer 和使用 nonblocking 然后加逻辑处理满了的情况。
    mmap 更多的时候是用于直接映射到 struct 指针这种邪教玩法。

    对于顺序写来说,mmap 只不过是隐藏了储存性能不足和 file write buffer 设置不当的问题。副作用是你彻底失去了对上述两者的控制。

    而且以一般的持久储存媒介的性能,少一两次拷贝不会有实质性的影响。
    nuk
        10
    nuk  
       2021-07-25 19:35:29 +08:00
    fwrite 内置 buffer,所以肯定是要加锁的,单线程写和 mmap 比性能可能差别不大,但是换到多线程场景,可能情况就不一样了,反正我认同内存 copy 肯定不是瓶颈,当然我就是猜猜而已。
    BBCCBB
        11
    BBCCBB  
       2021-07-25 19:58:58 +08:00
    不一定吧.. 你看 java 实现的 mq, 写入有用 mmap 的, 也有用 FileChannel write 的. fileChannel 这种批量 write 性能不一定比 mmap 差.. 看场景.

    可以参考 https://www.jianshu.com/p/d0b4ac90dbcb
    sagaxu
        12
    sagaxu  
       2021-07-25 20:00:37 +08:00 via Android
    @ipwx fwrite 才要 fflush,write 返回已经提交到内核了,就算进程奔溃,内核不知道该往哪里写了吗?

    @nuk posix 要求 write 写入不超过 PIPE_BUF 字节数时原子性,如果被写入的是 regular file,每次 write 都是原子性的,哪怕一次写 1GB 。所以只要 append 方式打开,尽管写入就是了,不用考虑并发。
    nuk
        13
    nuk  
       2021-07-25 20:10:40 +08:00
    @sagaxu 你说的是 write,不是 fwrite
    nuk
        14
    nuk  
       2021-07-25 20:18:51 +08:00
    ryd994
        15
    ryd994  
       2021-07-25 20:35:49 +08:00 via Android
    @nuk 那你多线程写 mmap 就不用锁了?
    ipwx
        16
    ipwx  
       2021-07-25 20:36:38 +08:00
    @ryd994 因为 mmap 内核管啊,内存哪怕崩溃了 mmap 刚放进去的东西还在,系统会记得把东西放进磁盘的。

    https://stackoverflow.com/questions/5902629/mmap-msync-and-linux-process-termination

    顺便 fprintf 要是没缓存不就更频繁写入文件了么。要是有缓存不就又二次缓冲了么(蛋疼)
    ipwx
        17
    ipwx  
       2021-07-25 20:37:31 +08:00
    @ryd994 另外什么少一两次拷贝影响的问题。flush 真正影响的是,机械磁盘的寻道很慢好不好。能缓冲写入的,就得缓冲写入,不然频繁小数据写入,拖累整个系统其他进程的速度。
    nuk
        18
    nuk  
       2021-07-25 20:50:28 +08:00
    @ryd994 mmap 可以做 lockless 的,如果你硬要说做日志队列的话,当我没说
    ryd994
        19
    ryd994  
       2021-07-25 21:40:55 +08:00 via Android
    @ipwx 你是不是忘了文件缓存也是由操作系统管理的?你 write 的时候是写入 page cache,flush 才是物理写入。如果你说的是进程挂了,那 write 进去的会在文件被关闭的时候自动 flush 。挂了也会由系统关闭所有文件。

    如果你说的是系统挂了,那不管 mmap 还是 write 都得挂。
    fwrite 是另一回事,因为 libc 可能另外有缓冲,但是你大可以不用 fwrite 。

    fprintf 也是这样。只是写入 fd,并不会物理写入。建议你再看看 write 的文档。

    @nuk 1.这里讨论的就是日志。2. 请解释 mmap 比 write 性能更好的理由。write 和 mmap 实际上都是操作 page cache 。除了 write 多一次拷贝之外,最终都是由系统管理何时写入物理媒介。
    其实多线程还要高性能写入,最好的一个线程一个
    ryd994
        20
    ryd994  
       2021-07-25 21:44:37 +08:00 via Android
    其实多线程还要高性能写入日志,最好的一个线程一个文件。事后再归并。大部分情况下系统时钟或者 monotonic 时钟就足够精度了。
    如果你要求绝对的时间顺序,那就最好用无锁队列或其他方式,然后把日志写入交给专门的线程。
    也可以开独立的日志进程,比如 syslog 。
    ryd994
        21
    ryd994  
       2021-07-25 21:55:34 +08:00 via Android
    @ipwx write 只保证写入 page cache 。vfs 还有 IO scheduler 都可以对操作进行重排或者合并。所以你就算随机写,只要范围不大而且最终结果是连续的,那操作系统就能够合并操作,结果还是连续的
    平时 write 会 block 是因为有缓存限制。但是这是可以调整的。
    而且还可以用 non blocking write 。缓存满了就失败,你自己再想办法处理。
    again,这些都是 page cache,由操作系统管理。

    如果你认真了解过 mmap 的实现机制,就会知道,mmap 实际上就是把 page cache map 给你了。既然 write 也是写入 page cache,那写入之后的事情就没有区别了。
    nuk
        22
    nuk  
       2021-07-25 22:52:46 +08:00
    @ryd994 老哥别揪着我不放了,我承认你说的都对
    swulling
        23
    swulling  
       2021-07-25 22:59:51 +08:00
    日志顺序写场景的话,mmap 的优势只不过是比内核少了一次拷贝,所以只有在极大数据量的情况下才有优势。
    Brentwans
        24
    Brentwans  
       2021-07-25 23:23:27 +08:00
    其它场景不太清楚,但是在数据处理的领域。如果单论写入性能,结论是 mmap 不会直接明显提升写入性能。因为不管什么写法瓶颈在 IO,只要写入速度到了磁盘 IO 上限,就再也无法提升。看了好多上面测试结论都有不准的可能,IO 性能的 benchmark 是不太容易的,需要用一定的技巧才行,因为系统数据缓存的原因,很容易最终测出的不是磁盘 IO 速度而是内存的速度。
    ryd994
        25
    ryd994  
       2021-07-25 23:47:06 +08:00 via Android
    其实你仔细看文一文二,说的都是安卓系统或者移动平台,也就是说,没有 root 权限,不一定能修改 page cache 回写的时机。那这种情况下用 mmap 就是不是办法的办法。因为 mmap 必定不阻塞,所以可以突破系统的限制。
    但是这说白了是没有权限的情况下的投机取巧。而且这种情况下对 mmap 也是有限制的。反复 map unmap 还有 page fault 的开销也不小。所以实际效果如何还有待商榷。
    julyclyde
        26
    julyclyde  
       2021-07-26 11:27:58 +08:00
    腾讯的问题在于:
    1 热爱 mmap
    2 立场高于实际
    codehz
        27
    codehz  
       2021-07-26 21:06:54 +08:00 via Android
    @ryd994 谁说 mmap 不阻塞的。只是内存足够给你浪而已。。。
    是可以卡在缺页中断那很久的,然后特定条件下可以影响业务稳定运行(你甚至没有任何办法打断一个处在在缺页状态的线程)。。
    无阻塞的文件读写接口只有 io uring 那里有,但是显然不够安全,进程炸了就没了
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   1570 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 27ms · UTC 17:12 · PVG 01:12 · LAX 09:12 · JFK 12:12
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.