使用mmap时如何查找缓存未命中
我需要在一些比较大的文件中进行搜索。这些搜索操作需要随机访问(可以想象成二分查找),所以我会使用mmap
来方便使用和提高性能。我的搜索算法会考虑到页面大小,这样每当我需要访问某个内存区域时,就能尽量利用好它。因此,有几个参数需要调整。我想找到那些能让我从块设备读取最少次数的参数。
我可以用纸和笔来做这个,但理论上的工作只能走这么远。实际环境中发生的事情很多,还有不同的页面缓存,这就复杂多了。有几个进程在访问这些文件,某些页面可能因为其他活动而在文件系统的页面缓存中可用。(我假设操作系统在使用mmap
时是知道这些的。)
为了看到我的搜索算法在从块设备读取的块数方面的实际性能,我需要知道在我的mmap
访问过程中发生了多少页面未命中。
理想的解决方案是能告诉我哪些内存区域的页面已经在缓存中。一个非常好的解决方案是有一个函数可以告诉我某个页面是否在真实内存中。这不仅能让我调整参数,甚至可能成为我算法的一部分(“如果这个页面在真实内存中,我们就提取一些信息,如果不在,那就读取另一个页面”)。
这个系统会在Linux(3系列内核)上运行,所以如果没有通用的答案,Linux特定的答案也是可以接受的。基准测试会用Python写,但如果接口只在C中存在,我也能接受。
例子
假设我们有一个文件,里面是固定长度的记录,记录中有一个排序的标识符和一些数据。我们想提取某个起始位置和结束位置之间的数据(由标识符定义)。最简单的解决方案是用二分查找找到起始位置,然后返回所有数据直到结束。
但是,如果我们需要考虑缓存,情况就有些不同了。直接的内存访问几乎是免费的,但页面未命中就很贵。一个简单的解决方案是用二分查找找到范围内的任意位置。然后可以向后遍历文件直到达到起始位置。接着再向前遍历文件直到结束。这听起来有点傻,但它确保一旦找到范围内的某个点,就不需要加载额外的页面。
所以,关键是找到范围内的一个位置。二分查找是个不错的选择,但如果我们知道,比如说,文件的前三个或后三个页面通常在页面缓存中,我们也应该利用这个信息。如果我们知道哪些页面在缓存中,搜索算法可以做得更好,但即使是事后知道我们是否命中或未命中也有帮助。
(实际问题比这复杂一些,但这也许能说明需求。)
部分解决方案:
正如JimB在他的回答中提到的,Linux中没有这样的API。这让我们只能使用一些更通用的性能分析工具(比如Python的cProfile
或者Linux中的perf stat
)。
我代码中的挑战是,我知道大部分时间会花在那些最终成为缓存未命中的内存访问上。这很简单,因为它们是代码可能会阻塞的唯一点。在代码中,我有类似b = a[i]
的东西,这个操作的速度会根据i
的不同而快或慢。
当然,看到在进程运行期间的总缓存未命中次数可能有助于一些优化,但我真的想知道系统的其他部分是否会造成一种情况,比如文件的首尾页面大部分时间都在缓存中。
所以,我会实现对关键内存访问(可能未命中的那些)的计时。由于系统中几乎所有的运行都是I/O限制(而不是CPU限制),因此上下文切换不太可能频繁影响我的计时。这不是一个理想的解决方案,但似乎是最不糟糕的选择。
1 个回答
这个问题其实需要在你的程序之外来处理。虚拟内存的管理是由操作系统的内核来负责的,程序本身并不能直接看到这些细节。你可以在程序运行时分析一下,看看函数调用的时间来估计发生了什么,但如果想真正了解情况,就需要使用特定于操作系统的分析工具。
在Linux系统中,有一个很不错的工具:perf
。使用perf stat
这个命令,可能就足够让你大致了解你的程序是如何执行的。