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

2021-07-25 16:02:11 +08:00
 lzjamao

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

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

log4a readme

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

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

4396 次点击
所在节点    程序员
27 条回复
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
2021-07-25 16:46:46 +08:00
然后楼主你举的那个日志的例子很特殊,是永远顺序往下写的。写满一个 4096 块就不会再动了,系统完全有机会每次写满 4096 再刷新。而且因为是 mmap,每次写一条日志就不需要 flush (因为你不怕内存崩溃),相当于避免了频繁 << 4096 大小的文件写入,自然变快了。

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

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

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

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

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

@nuk posix 要求 write 写入不超过 PIPE_BUF 字节数时原子性,如果被写入的是 regular file,每次 write 都是原子性的,哪怕一次写 1GB 。所以只要 append 方式打开,尽管写入就是了,不用考虑并发。
nuk
2021-07-25 20:10:40 +08:00
@sagaxu 你说的是 write,不是 fwrite
nuk
2021-07-25 20:18:51 +08:00
ryd994
2021-07-25 20:35:49 +08:00
@nuk 那你多线程写 mmap 就不用锁了?
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
2021-07-25 20:37:31 +08:00
@ryd994 另外什么少一两次拷贝影响的问题。flush 真正影响的是,机械磁盘的寻道很慢好不好。能缓冲写入的,就得缓冲写入,不然频繁小数据写入,拖累整个系统其他进程的速度。
nuk
2021-07-25 20:50:28 +08:00
@ryd994 mmap 可以做 lockless 的,如果你硬要说做日志队列的话,当我没说
ryd994
2021-07-25 21:40:55 +08:00
@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
2021-07-25 21:44:37 +08:00
其实多线程还要高性能写入日志,最好的一个线程一个文件。事后再归并。大部分情况下系统时钟或者 monotonic 时钟就足够精度了。
如果你要求绝对的时间顺序,那就最好用无锁队列或其他方式,然后把日志写入交给专门的线程。
也可以开独立的日志进程,比如 syslog 。

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/791638

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX