Clickhouse系列-番外-零拷贝

本文将向读者详细说明第三章中提到的无序存储时,每次读取需要读取4k的底层细节。第三章的附录已将向读者说明了“这个原因是因为操作系统在读取磁盘时,依据数据局部性原理,会按照页为单位读取,每页的大小默认是 4k。“本番外将向读者由此深入到一个计算机领域常用的一个优化——零拷贝技术。

在linux系统中,提供了3套API供应用执行文件操作:

  1. 系统调用
  2. 标准I/O
  3. mmap

第一种系统调用,是操作系统对外直接提供的文件API,提供对文件的字节读写操作。操作系统在其内部实现了页缓存机制,对应用端透明,操作系统根据访问页情况自行调整缓冲区大小。

第二种标准I/O,也就是大名鼎鼎的<stdio.h>。其通过流的方式实现对文件的操作。开发stdio的原因是因为系统调用的页缓存太大(16K~128K),而一些简单的应用,并不需要这么大的缓存,同时也由于调用系统调用涉及到CPU由用户模型向内核模式的切换,时间消耗比较大,因此开发了标准IO,可以看成是对内核的缓冲。

第三种mmap,就是所谓的零拷贝了。第二章标准IO适合简单的程序使用,但是对于数据库这样的对性能要求高的程序就会有个知名的缺点。标准IO本质是对第一种方式的缓冲,是通过在用户空间复制一份数据实现的,标准IO会将第一种系统调用read()生成的数据复制到用户空间一份,后续操作都在用户空间上进行操作,等待合适时机写会内核。此时,数据就发生了两次拷贝:即内核系统调用read()将数据复制到内核空间的内存上,和标准io将数据复制到用户空间。这也势必带来了性能损耗,幸好内核提供了mmap,支持应用将文件地址直接映射到当前进程的内存空间,这样应用就可以直接操作内存,由内核负责将内容同步到磁盘。

大部分数据库都使用mmap实现零拷贝,避免标准IO出现的两次拷贝的情况。不过mmap将文件内容映射到内存,而操作系统的内存管理单元(MMU)管理内存最小单位是页,因此mmap必须按照页的整数倍组织映射大小。这就是第三章计算中出现4K的原因。

使用mmap还有一个好处就是,除了少数的一些缺页异常,对mmap的读写都在用户空间进行。不会产生系统调用。此外,操作mmap还可以通过madvise()系统调用按需控制内核是否使用预读机制从而控制页缓存的大小。

mmap是现代应用程序应用非常广的一项技术,kafka的commitlog、postgresql的存储引擎……这些知名数据库都在大量应用零拷贝技术。但依然像我之前强调的那样,使用mmap也不是没有缺点,主要缺点是必须按照页的整数倍来组织大小,容易出现空间浪费。因此在处理大文件或者文件大小正好是pagesize的整数倍时,使用mmap会获得很大的性能提升。

2,254条评论

  1. I’ve been browsing online more than three hours today, yet I never found any interesting article like yours. It’s pretty worth enough for me. In my opinion, if all website owners and bloggers made good content as you did, the internet will be much more useful than ever before.

  2. Attractive part of content. I just stumbled upon your blog and in accession capital to assert that I get in fact loved account your blog posts. Any way I’ll be subscribing for your augment and even I success you access consistently rapidly.