我有分寸

mmap()和read()哪个快——linuxfb版聚上谈论的开发中的常见误解与陷阱

gnawux kernellinuxlinuxfb

本月版聚的规格最后是8人座谈 + 晚饭欢送 hzmangel (@hzmangel, @古月圣)同学南下,座谈比讲幻灯片更轻松了一点,不过,还是讨论了一些严肃问题,很多都是开发中的常见误解和误用。废话少说,一一列出,没列的出是我忘了,各位在场同学请补充。

mmap() 与 read() 哪个快

当Coly(@colyli, @淘泊松)抛出这个问题的时候,我们已经猜到 read() 快了,毕竟我们相信他会说些颠覆理解的东西,而且李凯(@leekayak)童鞋表示,他也听朱延海(@2002年一本漫画闯天涯)说过此事。那么,我们来听听 Coly 的解释:

  • 大家关于“mmap()”更快的认识来自于 read() 是需要内存拷贝的;
  • 当今硬件技术的发展,使得内存拷贝消耗的时间已经极大降低了;
  • 但“mmap()”的开销在于一次  pagefault,这个开销相比而言已经更高了,而且 pagefault 的处理任务现在比以前还更多了;
  • 而且,mmap之后,再有读操作不会经过系统调用,在 LRU 比较最近使用的页的时候不占优势;
  • 于是,普通读情况下(排除反复读之类的文艺与2B读操作),read() 通常会比 mmap() 来得更快。

mmap() 与 brk() 是否都会给分配的内存填0

这来自于李凯同学的一次提问,Coly同学断然否认了在这方面两者的不同,表示,自 kernel 0.9 以来,所有的分给用户的页全是初始化过的,没有用户数据的页一定会给0,这是一个安全问题,内核不会把不属于用户的内容给用户看的。而且,文件系统也是如此,空洞文件在读取未初始化过的文件内容时,返回的也一定是0。

当然,对于用户应用来说,C库提供的 malloc() 有可能会复用内存,而不是每次都从内核取,所以,有可能有未初始化内容。

sprintf() 的误用

提到初始化的时候,Coly 愤愤不平地表示,有童鞋居然这样初始化内存区域:

sprintf(buf,"");

并表示,这一操作的行为是未知、未定义的。我和Bergwolf(@oatgnep, @Bergwolf)、李凯现场测试了一下,在使用 -Wall 开关编译的时候,会报出 WARN,提示使用了空的格式字符串。这是 C 库标准未定义行为的情况,这样使用会导致不可预期的情况出现,为什么不直接给 buf[0] 赋值 '\0' 呢。

memset() 初始化内存有什么问题(以及硬盘的FUA/DPO)

在场童鞋表示,他们都不会这么写 sprintf(),hzmangel 童鞋表示,他会用 memset() 填 0,于是,又引出了 Coly 的话题。他表示,这样操作在正确性上没有问题,但是会破坏 Cache Line,这些 0 区域将来会被重新写入,是完全没有读意义的,如果他们冲掉了正在很热的被访问的 Cache 中的内容,对于那块内存的访问可能对运行时间有严重的影响。所以,如果一定要写入,最好能够告诉 CPU,默默地写入,不要干扰 L1 Cache,这样可能会让写入更慢,但如果同时有其他 VIP 读 Cache 的内容的话,会因此受益。(怎么干我忘了,谁告诉我哈)

Update:Bergwolf 提醒,这一绕过 Cache 的手段称为 non-temporal write,详见这里:http://lwn.net/Articles/255364/

同时,对磁盘的访问也是如此,某些操作不希望写入硬盘的Cache,防止影响读取告诉缓存的数据,这样的写操作可以利用 SCSI 的 FUA/DPO 标识,在新内核中,对于支持 FUA/DPO 的设备,Ext4的 journal 会使用这种方式写入,而非 barrier,从而提高性能。

nanosleep() 的开销在新 kernel 上为什么会变大(任何Sleep都会至少睡1毫秒?)

Coly 同学还提到了在他们的新 Kernel 上,老应用遇到的新问题。其中之一是某应用抱怨 sleep 开销变大,追查下去,是有四个线程频繁地 nanosleep 10 微秒,导致抢锁造成的。

“关于 sleep 一次至少会消耗1-2毫秒的认识早已经过时了”

这个问题来自于早期程序员们坚信,sleep会让出CPU,由于调度问题,会有至少1-2毫秒的消耗,于是,甚至会有人用 sleep(0)。但是,高精度计时器(HPET)的引入使得 sleep 的精度已经大幅提升了,你要10微秒,就会给你 10 微妙,于是,锁的时候的 spinlock 争抢变得异常激烈了……

这个故事告诉我们,那些没有明确承诺过的 feature,也会随着时间的推移,悄悄地消失,成为陷阱,要么避开不用,要么总是留意。

iowait% 大了,是否是开销就变大了

这也是一个新 kernel 带来的问题,hadoop 运维童鞋会观测到 iowait 比原来的老 kernel 高了 10% 以上,这个究竟是不是问题呢。Coly告诉我们,请看看运行时间——实际任务的运行时间会快 10%……这是为什么呢?

这个,解释下 wait time 是怎么计算的:内核每次采样看每个 CPU 的状态,如果 CPU 上是 idle,而且有任务在等 IO,就标记为 wait,那么,假设有16个CPU,如果有8个任务,分到16个CPU上,一直等IO,就是 50%,而如果有10个任务,分到两个 CPU上,就是 12.5%。这说明了什么问题呢——wait 并不全面反映等 IO 的严重性,新 kernel 更倾向于均匀地把任务分到更多 CPU 上,于是看起来 wait 值就高了。

这涉及到一个更科学的数据观的问题——你更关注的应该是任务的执行时间和效率、throughput、qps,而不是 CPU 的 wait time。

另外,iowait 时间是从 idle 中分化出来的,某些老的监控程序会直接用 (100% - idle%)当成 CPU 占用率,在 iowait 独立出去之后,会显得占用率变高。

其他

已经写了不少了,其他的不是我不想写,是想不起来了……请补充啊。

另外,Coly 强烈感谢了马涛(@淘泊瑜)为首的淘宝内核组童鞋们(羡慕嫉妒恨地说,他们还有美女 kernel 程序员……),作为国内互联网公司内,并入主线的 kernel patch 量当之无愧的老大,他们也是我们版聚的很多话题的来源,linuxfb 也感谢你们。

 

gnawux
me!#$!@#$@#$wangxu!@#$%^&*()_me