写mmap内存变慢的原因
有很多系统读写大文件时用的是这个办法:将大文件mmap到内存,然后直接对内存读写。这样就化read/write为memcpy操作,代码开发上很简便。被修改的内存页由kernel负责挑个时间写入硬盘,程序员不用操心。
但是,最近一些使用了taobao kernel(基于redhat6-2.6.32)的机器,上面那些使用mmap的应用变慢了。我们上线查看,才发现mmap文件里有很多脏页,kernel的writeback机制就不停的将这些脏页写往硬盘,结果造成了大量的io(从iostat看,除了几秒的间歇,io util几乎保持在100%),而如果换回2.6.18内核,就没有这个问题(io util不超过20%,而且很稀疏)。
为了简化应用模型,我们做了一个mmap_press程序模拟应用的写操作,内容很简单,就是mmap一块256MB的内存,然后在256MB的范围内随机的写,一次写8个字节,一共写25亿次,在rhel5(kernel-2.6.18)上,这个程序运行只需要374秒,而在我们的内核上,则要805秒才能完成,慢了两倍多。再换upstream的内核,一样慢,这个问题应该是一直有的。
看来自从writeback改成per bdi的以后,脏页写回的力度是大大加强了。加强是有原因的:越快写回脏页,页面新数据丢失的肯能就越少。但问题是,现在writeback的太频繁了,结果消耗了大量io,拖慢了应用。
能不能找个办法通知writeback子系统,让它每隔60秒或两分钟才开始写脏页,这样至少很多相邻的脏页能被合并成一次io,可以变大量小io为几个大io,速度会快很多。于是我们找到了这个参数 /proc/sys/vm/dirty_expire_centisecs ,默认值是3000,即30秒,也就是说,脏页要过了30秒才会被写往硬盘....等一等,这和我们观察到的完全不一样啊!?我们从iostat看到的是io util一直保持在100%,只有几秒的停歇,几秒啊,不是30秒。
多猜无益,writeback子系统可是相当大的一块,于是我们联系了Intel的吴峰光,他第二天就给出了两个patch,我们将其移植到2.6.32内核后(1,2),效果很明显,writeback不再是不停的制造io,而是5~6秒的集中io以后,就停下来大约30秒(这次符合dirty_expire_centisecs参数的默认值了),然后再开始5~6秒的集中io,如此循环。我们重新跑mmap_press程序,耗费时间是390秒,已经非常接近2.6.18的速度了。
感谢吴峰光同学的帮助。
大概看了一下吴峰光patch的注释:之前writeback在回写完一个文件后,会从头再查找一遍脏页,如果有脏页则继续回写;现在改成,回写到文件尾后,直接停下来,直到脏页expire(也就是30秒后了)再开始从头检查脏页并回写(这是我对patch的解释,肯定有纰漏之处,有兴趣的同学还是直接看一下patch)。原来如此,咱们的mmap操作有大量的随机写,产生了大量分散的脏页,writeback每次从头检查文件都发现脏页,结果每次都要从头开始回写,就这么不停的转着圈的回写,造成io几乎一直保持在100%。
但我还是有一个疑问:应用写内存只是一个内存操作,writeback写脏页只是一个硬盘操作,为什么硬盘操作会拖慢内存操作呢?最后万能的马涛同学给出了答案:应用对页面的写会触发内核的page_mkwrite操作,这个操作里面是会lock_page的,如果page正在被写往硬盘,那这时候它已经被writeback给lock了,page_mkwrite的lock_page会被阻塞,结果应用写内存的操作就顿住了,所以,越是频繁的writeback,越是会拖慢应用对mmap内存的写操作。
=== 2012.2.29 ===
Bergwolf:
nitpicky一下:) writeback的时候page只是会被mark成PG_WRITEBACK,而不是被lock住。所以page_mkwrite的等待很可能是在wait_for_page_writeback上而不是lock_page上。
RobinDong:
我看了一下upstream的代码,以ext4文件系统的mmap为例,writeback时,write_cache_pages里先lock_page(mm/page-writeback.c 2241行),再调用ext4_writepage来写单个页面,ext4_writepage里调用block_write_full_page来把页面mark成PG_WRITEBACK。我这里看来,是先lock后mark。
另外,在我们这边的2.6.32老内核里,__block_page_mkwrite里没有调用wait_on_page_writeback,这个是去年五月份才加进去的。所以,2.6.32 mmap变慢的原因应该还是lock_page的争抢。
Bergwolf:
page_mkwirte应该只在page第一次被写的时候调用,线上应用的page应该都是在内存中了,为什么你们认为page_mkwrite和writeback有竞争呢?你的测试代码是新建一个sparse文件,有试过对一个已经在内存中的dense文件测试吗?
RobinDong:
我们用的就是已经在内存中的dense文件,之前我也以为只有page第一次进入内存时才有do_page_fault才有page_mkwrite,但是马涛哥颠覆了我的世界观——writeback完成后的page,在被写的时候就会调用page_mkwrite——代码路径我们今早也找到了。
在write_cache_pages里,lock_page以后会调用clear_page_dirty_for_io(page),然后一路
--> clear_page_dirty_for_io(page)
--> page_mkclean(page)
--> page_mkclean_file(page)
--> page_mkclean_one(page)
下来,一直到page_mkclean_one(page),里面会做pte_wrprotect(entry),也就是把pte置为“写保护”,这样做是故意的,当应用写这个page时,就会因为碰了“写保护”页面而触发do_page_fault,即使这个page已经是在内存中了。
-->do_page_fault
--> handle_mm_fault
--> handle_pte_fault
handle_pte_fault这段代码:
if (flags & FAULT_FLAG_WRITE) {
if (!pte_write(entry))
return do_wp_page(mm, vma, address,
pte, pmd, ptl, entry);
entry = pte_mkdirty(entry);
}
如果用户是在尝试写页面(FAULT_FLAG_WRITE),且pte是写保护的(!pte_write(entry)),那么就调用do_wp_page,而do_wp_page里面对可写且共享的vma里的page,会依次调用page_mkwrite。
相关文章
- [kernel] exit_mmap BUG_ON() - 06 06, 2012
- [kernel] 内核缓冲的锁 - 08 11, 2010
- [kernel] 如果在linux内核模块里用错锁? - 08 04, 2010
Orz,这个blog解释的来龙去脉好清楚,连我这种kernel小白也能看的相当流畅,呵呵:)
不知道Fengguang当初是怎么能那么快就定位出这个细节并fix的?
Ps:patch的链接外面的人访问不了貌似?
????一下, 您所描述的???}在Debian 6 (2.6.32)下也???l生???
@passenger, 我没有测过debian,但是考虑到Fengguang同学的patch还没有进upstream,我猜测debian 6的2.6.32很可能也有同样的问题。您可以用mmap_stress测一测,看看io util是不是一直很高。
我想起去年测过这个问题,不同的是当时我是写了一个内核模块来测的。大规模的mmap,应该是搜索应用吧?呵呵
@壮壮粑粑, 搜索是比较典型的用mmap的应用,但是不只,还有很多应用也是如此 :)
@谢良,Fengguang对writeback肯定是了如指掌,又有可以快速复现问题的程序,所以应该是比较快fix的,但是社区不见得会收这个patch,我估计社区会说:“如果你不想让writeback发生,那就直接用共享内存或ramfs好了,干嘛偏要用硬盘文件的mmap?“。
另外,那个patch之前确实外部无法访问,我改了链接,现在应该可以了。
nitpicky一下:)
writeback的时候page只是会被mark成PG_WRITEBACK,而不是被lock住。所以page_mkwrite的等待很可能是在wait_for_page_writeback上而不是lock_page上。
欢迎 @Bergwolf (http://weibo.com/bergwolf)哥来访 !,可惜不是坐在一个办公室没法当面交流,但我有很多话要说,这样吧,我把你的comment附到正文上去,再在正文里回复。当然,如果你还发现了漏洞,欢迎继续comment :)
@DongHao 恩,是我没有看仔细。我原本认为在writeback的时候lock_page的时间相比非常短,所以我觉得在lock_page上冲突的概率比较小。但是我没有注意到你的testcase是写一个256MB的文件。对小文件的密集随机写,lock_page冲突还是很可能的。
另外我还有一个疑问,page_mkwirte应该只在page第一次被写的时候调用,线上应用的page应该都是在内存中了,为什么你们的认为page_mkwrite和writeback有竞争呢?你的测试代码是新建一个sparse文件,有试过对一个已经在内存中的dense文件测试吗?
@Bergwolf,“page_mkwirte应该只在page第一次被写的时候调用,线上应用的page应该都是在内存中了,为什么你们的认为page_mkwrite和writeback有竞争呢”,你问到点子上了,这也是我之前的问题,我测试用的就是一直在内存中的dense文件,既然都在内存中,怎么会反复的调用page_mkwrite?马涛哥的回答是:只要page不是dirty,在写page时就会触发page_mkwrite。说实话,我的世界观被颠覆了,我一直以为只有page不在内存中时,才会触发do_page_fault,然后page_mkwrite。
我翻了一下代码,暂时还没找到马涛哥所说的逻辑对应的代码,我再继续找找,找到了再分享一下。
dirty_writeback_centisecs 有试过么 ?per bdi thread会去schedule()。
@DongHao 我的世界观也被颠覆了:)赞万能的马涛哥,学习了,多谢分享~~
@sunzixun,这个参数我们试过,dirty_writeback_* 相关的几乎所有参数我们都试过,但是如果没有峰光的patch,这些参数的调整都解决不了问题。因为一旦writeback开始转着圈的扫描文件脏页,它就不会理睬这些参数,“转圈”这个是没参数配的。
pte_wrprotect 这一招比较意思啊。。
lz 开始对 dirty_expire_centisecs
理解有误,它是说最后的情况,而不是仅有的
我明白你的情况了 。谢谢
@sunzixun, "dirty_expire_centisecs 理解有误,它是说最后的情况,而不是仅有的",噢,请问正确的理解是?
楼主,能否更新下链接,不能用?
@yt,你指的是哪个链接?我试了一下,都是好的呀
看来2.6.32内核中引进的per-bdi-writeback引发的性能回退还是很普遍的,我在最近的工作中也遇到产品写性能下降的问题,最终也定位到这里:http://hi.baidu.com/casualfish/item/8bd452a6463a83268919d39e
正如你所说,“转圈”写回是问题的根源,吴峰光的io限流功能patch(3.2内核中已经merge了这个patch)只是减小了flush的频率,也没有完全解决这个问题。
“如果你不想让writeback发生,那就直接用共享内存或ramfs好了,干嘛偏要用硬盘文件的mmap?",不只是mmap,频繁的写入和修改文件同样会引发性能的问题,这点在目前只能通过调整用户空间的文件写入策略来规避了。
lz说的去年五月份加进去的功能应该是stable pages,Ted Ts'o merge了这个功能:http://thread.gmane.org/gmane.linux.kernel.mm/62764/focus=63383,
lwn上有对应的介绍:http://lwn.net/Articles/486311/
有意思的是,提出这个patch引起性能回退的也是Ted Ts'o:http://thread.gmane.org/gmane.linux.file-systems/62226
个人觉得还是callback的实现模式比较好,不用block进程而且比较轻量。
我这也有个类似的内存映射的问题请教您。在redhat 2.6.18内核4G物理内存环境下,用mmap映射一块内存空间做bloom过滤,一个bloom查找会将这块内存中22个bit位置为1。开始映射一个1G大小的内存10万次查找耗时很少,但当将映射内存变为2G后,同样进行10万次查找耗时却明显增加,查看io util为100%,我在更高内存的机器上(16G内存)测试得出同样的结论:只要映射的内存变大则耗时就会急剧增加,请帮忙分析下原因,谢谢!
io util 100%,那我猜就只能是writeback在写硬盘了,你可以试一试
echo 0 > /proc/sys/vm/flush_mmap_pages
这是rhel5的一个trick,设成0以后,mmap的内存即使脏了也不会回写。这样做有一点风险,就是如果突然宕机,内存里的新数据就丢了。你可以写个crontab,在系统压力小的时候做一下sync。
还有一个办法就是把你要改的那些bit凑在一起(而不是分布在mmap内存的不同地方),这样回写就是连续写磁盘,压力也会好很多。
我想问下,你们都是在ext3下测试的么? rhel6默认都是ext4了吧,ext4有同样问题么?
这是writeback的机制造成的,跟文件系统没有关系,如果writeback回写得太频繁,你用什么文件系统都有一样的问题