Linux 内核 - msync 锁定行为

问题描述

我正在研究将固定大小块(例如 4k)中的随机数据写入大型缓冲区文件中的随机位置的应用程序。我有几个进程(不是线程)这样做,每个进程都有自己分配的缓冲区文件

如果我使用 mmap+msync 将数据写入磁盘并将其持久化到磁盘,我会看到 16 个进程的性能峰值,以及更多线程(32 个进程)的性能下降。

如果我使用 open+write+fsync,我看不到这样的峰值,而是性能稳定(并且 mmap 比 open/write 慢)。

我多次阅读 [1,2] 说 mmap 和 msync 都可以获取锁。使用 vtune,我分析我们确实是自旋锁,并且在 clear_page_ermsxas_load 函数上花费的时间最多。

但是,在阅读 msync [3] 的源代码时,我无法理解这些锁是全局锁还是每个文件锁。论文 [2] 指出锁位于内核中每个文件的基数树上,但是,当我观察到内核中的一些自旋锁时,我相信有些锁可能是全局的,因为我每个文件都有一个文件过程。

你有没有解释一下为什么我们在 16 个进程中对 mmap 和 msync 的锁定行为的输入有如此大的峰值?

谢谢!

最好的, 马克西米利安·博特

[1] https://kb.pmem.io/development/100000025-Why-msync-is-less-optimal-for-persistent-memory/

[2] 优化快速存储设备的内存映射 I/O,Papagiannis 等人,ATC '20

[3] https://elixir.bootlin.com/linux/latest/source/mm/msync.c

解决方法

在 4.19 内核源代码中,当前任务保留自己的 mm_struct,其中包含一个信号量,用于仲裁对所有同步内存区域的访问。因此,作用于您的缓冲区文件之一的进程中的所有线程都将使用该信号量,对文件的某些区域进行操作,并释放该信号量。

虽然我无法解释达到性能悬崖的 16 个进程的确切数量,但很明显,当您使用 mmap() 时,您是在强制进入 VM_SHARED 的 msync(M_SYNC) 代码部分。这会调用 vfs_fsync_range() 并保证实际的同步磁盘 I/O 会发生,这通常会减慢显示速度:它不允许对 I/O 进行有利的分组以节省成本,并且往往会最大化等待磁盘 I 所花费的实际时间/O 完成。

为避免这种情况,请确保您的进程中的每个线程管理缓冲文件中 4K 块的专用子集,避免在缓冲文件上使用 mmap() 并调度异步 I/O。只要您避免在缓冲区文件本身上使用 mmap(),每个线程将单独写入(安全地,如果设计得当)到缓冲区文件的自己的部分。因此,您将能够将所有 I/O 指定为异步,这应该允许更好的聚合并显着提高您的应用程序的性能,即避免在 16 个进程或任何最终数量时出现悬崖。显然,如果对同一块的另一个写入请求,您仍然必须确保写入其中一个块的任何线程要么完成该写入,要么尚未开始它(并丢弃任何这样做的请求)来了。

如果此回答对您有帮助,请标记为已接受。祝您好运,调整您的应用程序的性能!