如何可移植地扩展使用mmap()访问的文件

我们正在尝试改变embedded式数据库系统SQLite,使用mmap()而不是通常的read()和write()调用来访问磁盘上的数据库文件。 整个文件使用一个大的映射。 假设该文件足够小,以至于我们在虚拟内存中找不到空间。

到现在为止还挺好。 在很多情况下,使用mmap()似乎比read()和write()更快一些。 而且在某些情况下要快得多。

调整映射的大小以提交扩展数据库文件的写入事务似乎是一个问题。 为了扩展数据库文件,代码可以做这样的事情:

ftruncate(); // extend the database file on disk munmap(); // unmap the current mapping (it's now too small) mmap(); // create a new, larger, mapping 

然后将新数据复制到新内存映射的末尾。 但是,munmap / mmap是不可取的,因为这意味着下一次访问数据库文件的每个页面时会发生次要页面错误,并且系统必须searchOS页面caching以find与虚拟内存地址关联的正确帧。 换句话说,它减慢了随后的数据库读取。

在Linux上,我们可以使用非标准的mremap()系统调用来代替munmap()/ mmap()来调整映射的大小。 这似乎避免了小页面错误。

问题:在没有mremap()的其他系统上如何处理这个问题呢?


目前我们有两个想法。 还有关于每个问题:

1)创build大于数据库文件的映射。 然后,在扩展数据库文件时,只需调用ftruncate()将文件扩展到磁盘上并继续使用相同的映射。

这将是理想的,似乎在实践中工作。 但是,我们在man页面中担心这个警告:

“未指定在与添加或删除的文件区域相对应的页面上更改映射的基础文件大小的效果”。

问:这是我们应该担心的事吗? 或者在这个时代是不合时宜的?

2)扩展数据库文件时,使用mmap()的第一个参数来请求与当前映射在虚拟内存之后的数据库文件的新页面对应的映射。 有效地扩展初始映射。 如果系统无法遵从第一个放置新映射的请求,则回退到munmap / mmap。

在实践中,我们已经发现OSX在这种方式上定位映射是相当不错的,所以这个技巧在那里工作。

问题:如果系统确实在第一个虚拟内存之后立即分配第二个映射,那么使用munmap()中的一个大的调用来最终取消映射它们是否安全?

Solutions Collecting From Web of "如何可移植地扩展使用mmap()访问的文件"

  1. 在可用的地方使用fallocate()而不是ftruncate()。 如果没有,只需在O_APPEND模式下打开文件,并通过写入一些零来增加文件。 这大大减少了碎片。

  2. 如果可用,使用“巨大的页面” – 这大大减少了大映射的开销。

  3. pread()/ pwrite()/ pwritev()/ preadv()与不那么小块大小真的不是很慢。 比IO实际执行速度要快得多。

  4. 使用mmap()时的IO错误将生成段错误,而不是EIO左右。

  5. SQLite WRITE性能问题的大部分集中在良好的事务性使用(即,当COMMIT实际执行时应该进行调试)。

  1. 我认为#2是目前最好的解决方案。 除此之外,在64位系统上,你可以在一个OS永远不会选择映射的地址(例如Linux中的0x6000 0000 0000 0000)处明确地创建映射,以避免操作系统无法在第一个映射之后立即放置新的映射一。

  2. 用一个munmap调用取消映射多个映射是安全的。 如果您想这样做,您甚至可以取消映射一部分映射。