如何故意触摸存储页面?

时间:2013-09-21 04:50:27

标签: c++ c multithreading memory-mapped-files

首先,在有人抱怨之前,我意识到在理论上完美的C ++代码的角度来看,内存模型是一个我不应该依赖的实现细节。但是,我赞成表现而非纪律。

这是一个场景:我有一个地址空间区域,我告诉操作系统使用我选择的文件返回 - 也就是说,文件是内存映射的。如果我对VMM通常如何工作的理解是正确的,操作系统可能对将页面加载到我的映射中非常懒惰,并且可能只在页面实际被触摸时才这样做。

通常我可以忽略这个细节,但在这种特殊情况下,我将映射数据发送到工作线程池。如果我只是天真地向worker发送一个指向这个缓冲区的指针,那么当第一次触摸页面时,工作线程本身很可能会遇到页面错误,这将导致工作程序阻塞直到页面处于物理状态由VMM加载。

工作池的设计使得其线程在I / O上阻塞是非常糟糕的,而在作业中发送的线程可以容忍被阻塞。因此,我希望我的发送方线程首先触摸映射的页面,以便页面错误会阻止它。

(我知道没有保证首先触摸页面将停止工作线程中的后续页面错误,但程序在大多数情况下仍然是最佳的并且一直在纠正。)

在x86汇编语言中,这将是微不足道的:

; get the page's address in ebx
mov al, Byte Ptr [ebx]

不幸的是,它在C或C ++中并不那么简单。一个简单的实现很简单:

char *pPage = ...;
char Dummy = *pPage;

但是,这可能不会起作用,因为任何自尊的优化器都会意识到代码什么也不做,只是省略它。

我们可以使用内联汇编,但这可能严重削弱优化器。我们可以调用汇编语言函数来执行它,但是我们不必要地(通常很小的)函数调用开销。

我们可以使Dummy成为一个外部可见的变量,这样可行,因为编译器不能认为赋值是无意义的。但是,这可能会导致争用缓存持有Dummy的CPU缓存行,从而严重降低多核系统的性能。 (更不用说,我们浪费了缓存行和访问权限。)

我也想过这样做:

char volatile *pPage = ...;
char Dummy = *pPage;

我知道volatile关键字有两个保证:

  • 编译器不会重新排序访问;以及

  • 编译器不会假设连续读取之间的值相同。

但是,这似乎并不能保证编译器即使不需要它也会读取该值。

有什么想法吗?

1 个答案:

答案 0 :(得分:4)

保证

volatile按定义执行内存访问,因此一个简单的解决方案就是您的建议:

volatile char *prefetch_me = ...;
(void)*prefetch_me;

但是,如果您想以(可能)更有效的方式触摸多个页面(并且您在* ix系统上运行),请查看madvise(),特别是MADV_WILLNEED和/或MADV_SEQUENTIAL。从手册页:

  • MADV_WILLNEED - 预计在不久的将来访问。 (因此,提前阅读一些页面可能是个好主意。)
  • MADV_SEQUENTIAL - 按顺序预期页面引用。 (因此,可以提前积极地读取给定范围内的页面,并且可以在访问它们后立即释放它们。)