mmap()与阅读块


184

我正在开发一个程序,该程序将处理大小可能为100GB或更大的文件。这些文件包含可变长度记录集。我已经启动并运行了第一个实现,现在正寻求提高性能,尤其是由于输入文件被扫描了多次,因此更有效地执行I / O。

是否有mmap()通过C ++ fstream库读取和使用块的经验法则?我想做的是从磁盘将大块读取到缓冲区中,从缓冲区中处理完整的记录,然后再读取更多内容。

mmap()代码可能会得到非常混乱,因为mmap“d块需要躺在页大小的边界(我的理解)和记录可能潜在般划过页面边界。使用fstreams时,由于我们不限于读取位于页面大小边界上的块,因此我只能寻求记录的开头并再次开始读取。

我如何在这两个选项之间做出选择,而无需先实际编写完整的实现?有任何经验法则(例如mmap()快2倍)还是简单测试?


1
这是一个有趣的读物:medium.com/@sasha_f/…在实验mmap()中,比使用syscalls快2-6倍read()
mplattner

Answers:


208

我试图找到有关Linux上的mmap /读取性能的最终答案,并且在Linux内核邮件列表中遇到了一篇不错的文章(link)。这是从2000年,所以出现了很多改进,IO和自那时以来,内核虚拟内存,但它很好地解释了为什么原因mmapread可能更快或更慢。

  • 与的调用mmap要比拥有更多的开销read(就像epoll相比而言poll,拥有更多的开销,而相比,具有更大的开销read)。出于某些原因,更改虚拟内存映射在某些处理器上是一项非常昂贵的操作,原因是在不同进程之间进行切换很昂贵。
  • IO系统已经可以使用磁盘高速缓存,因此,无论您使用哪种方法,如果读取文件,都会访问高速缓存或错过高速缓存。

然而,

  • 对于随机访问,内存映射通常更快,尤其是在您的访问模式稀疏且不可预测的情况下。
  • 内存映射使您可以继续使用缓存中的页面,直到完成操作为止。这意味着,如果长时间使用大量文件,然后将其关闭并重新打开,页面仍将被缓存。使用read,您的文件可能早已从高速缓存中清除了。如果您使用文件并立即丢弃它,则此方法不适用。(如果您尝试mlock仅将页面保留在高速缓存中,则说明您试图使磁盘高速缓存不明智,而这种愚蠢的做法很少会提高系统性能)。
  • 直接读取文件非常简单快捷。

mmap / read的讨论让我想起了另外两个性能讨论:

  • 一些Java程序员震惊地发现,非阻塞I / O通常比阻塞I / O慢,如果您知道非阻塞I / O需要进行更多的系统调用,这是很合理的。

  • 其他一些网络程序员震惊地发现它epoll通常比慢poll,如果您知道管理epoll需要进行更多的系统调用,这是很合理的。

结论:如果您随机访问数据,将其保留很长时间,或者您知道可以与其他进程共享(MAP_SHARED如果没有实际共享,就不会很有趣),请使用内存映射。如果您顺序访问数据或在读取后将其丢弃,则可以正常读取文件。并且,如果这两种方法都使您的程序不那么复杂,请执行此操作。在许多现实情况下,如果不测试您的实际应用程序而不是基准,就无法确定显示更快的方法。

(对这个问题的回答很抱歉,但是我一直在寻找答案,并且这个问题一直出现在Google搜索结果的顶部。)


请记住,如果不经过测试就使用2000年代以来基于硬件和软件的任何建议将是一个非常可疑的方法。而且,尽管关于该线程的mmapvs的许多事实read()仍然像过去一样正确,但总体性能并不能真正通过综合利弊来确定,而只能通过对特定硬件配置进行测试来确定。例如,“对mmap的调用要比读取更多的开销”是有争议的-是的mmap,必须将映射添加到进程页表,但是read必须将所有读取的字节从内核复制到用户空间。
BeeOnRope

结果是,在我的(现代的Intel,大约于2018年)硬件上,mmap其开销要小于read大于页面大小(4 KiB)的读取。现在确实如此,如果您想稀疏地随机访问数据mmap,那确实非常好-但这不是必须的,相反:mmap对于顺序访问而言,它仍然是最好的。
BeeOnRope

1
@BeeOnRope:您可能会对基于2000年代的硬件和软件的建议持怀疑态度,但我对那些未提供方法论和数据的基准持怀疑态度。如果您想提出一个mmap更快的案例,我希望至少能看到整个测试设备(源代码)及其列表结果和处理器型号。
Dietrich Epp '18

@BeeOnRope:还请记住,当您像这样测试内存系统的某些位时,微基准测试可能极具欺骗性,因为TLB刷新会负面影响程序其余部分的性能,并且如果您只能测量mmap本身。
Dietrich Epp '18

2
@DietrichEpp-是的,我会精通TLB效果。请注意,mmap除非在特殊情况下(但munmap可能),否则不会刷新TLB 。我的测试既包括微基准测试(包括munmap也包括在实际用例中运行的“应用程序中”。当然,我的应用程序与您的应用程序不同,因此人们应该在本地进行测试。甚至还不清楚mmap微基准测试是否受其青睐:read()由于用户端目标缓冲区通常停留在L1中,因此微基准测试也获得了很大的提升,这在大型应用程序中可能不会发生。是的,“很复杂”。
BeeOnRope

47

主要的性能成本将是磁盘I / O。“ mmap()”当然比istream快,但是这种差异可能并不明显,因为磁盘I / O将主导您的运行时。

我试着本柯林斯的代码段(见上文/下文),以测试他断言,“mmap()的是方式更快”,并没有发现可测量的差异。看到我对他的回答的评论。

我当然建议您依次依次映射每个记录,除非您的“记录”非常大-这太慢了,需要对每个记录进行2次系统调用,并且有可能使页面从磁盘内存缓存中丢失。 。

在您的情况下,我认为mmap(),istream和低级别的open()/ read()调用几乎都是相同的。在以下情况下,我建议使用mmap():

  1. 文件中存在随机访问(非顺序访问),并且
  2. 整个文件都适合放在内存中,或者文件中存在引用位置,以便可以将某些页面映射到其他页面中。这样,操作系统就可以使用可用的RAM来获得最大的收益。
  3. 或者,如果多个进程正在读取/在同一个文件上工作,则mmap()很棒,因为这些进程都共享相同的物理页面。

(顺便说一句-我爱mmap()/ MapViewOfFile())。


关于随机访问的好处:这可能是推动我的感知的因素之一。
本·柯林斯

1
我不会说文件必须舒适地放入内存中,而只能放入地址空间中。因此,在64位系统上,应该没有理由不映射大型文件。操作系统知道如何处理。它与交换使用的逻辑相同,但是在这种情况下,不需要磁盘上的其他交换空间。
MvG

@MvG:您了解有关磁盘I / O的观点吗?如果文件适合地址空间而不适合内存,并且您具有随机访问权限,那么您可能需要进行每个记录访问,都需要移动和搜索磁盘磁头或执行SSD页面操作,这对于性能而言是一个灾难。
蒂姆·库珀

3
磁盘I / O方面应独立于访问方法。如果您真正可以随机访问大于RAM的文件,则mmap和seek + read都严重受磁盘限制。否则,两者都会从缓存中受益。我不认为文件大小与内存大小相比在任何方向上都是有力的论据。另一方面,文件大小与地址空间的关系是一个非常有力的论据,尤其是对于真正的随机访问而言。
MvG 2014年

我原来的回答有并指出了这一点:“整个事情都可以轻松地放入内存中,或者文件内有引用位置”。所以第二点可以解决您的意思。
蒂姆·库珀

43

MMAP是方式更快。您可以编写一个简单的基准来向自己证明:

char data[0x1000];
std::ifstream in("file.bin");

while (in)
{
  in.read(data, 0x1000);
  // do something with data
}

与:

const int file_size=something;
const int page_size=0x1000;
int off=0;
void *data;

int fd = open("filename.bin", O_RDONLY);

while (off < file_size)
{
  data = mmap(NULL, page_size, PROT_READ, 0, fd, off);
  // do stuff with data
  munmap(data, page_size);
  off += page_size;
}

显然,我遗漏了细节(例如,如果文件不是的倍数,那么如何确定何时到达文件末尾page_size),但实际上,它不应该比这复杂得多。

如果可以的话,您可以尝试将数据分解为多个文件,这些文件可以整体而不是部分通过mmap()编辑(更简单)。

几个月前,我对boost_iostreams的滑动窗口mmap()-ed流类进行了半熟的实现,但是没人关心,我开始忙于其他工作。最不幸的是,几周前我删除了一个旧的未完成项目的档案,这是受害者之一:-(

更新:我还应该添加一个警告,即该基准在Windows中看起来将完全不同,因为Microsoft实现了一个漂亮的文件缓存,该缓存首先执行了mmap的大部分操作。即,对于经常访问的文件,您可以执行std :: ifstream.read(),它的速度与mmap一样快,因为文件缓存已经为您完成了内存映射,并且它是透明的。

最终更新:大家好:在OS和标准库以及磁盘和内存层次结构的许多不同平台组合中,我不能肯定地说mmap,被视为黑匣子的系统调用始终总是总是快得多。比read。即使我的话可以这样解释,这也不是我的意图。 最终,我的观点是,内存映射的I / O通常比基于字节的I / O更快。这仍然是事实。如果您实验性地发现两者之间没有区别,那么对我来说唯一合理的解释是您的平台在幕后实施了内存映射,从而有利于执行对call的调用。read。绝对确定您以可移植方式使用内存映射I / O的唯一方法是使用mmap。如果您不关心可移植性,并且可以依靠目标平台的特定特性,那么使用它read可能会很合适,而不会显着降低任何性能。

编辑以清理答案列表: @jbl:

滑动窗口mmap听起来很有趣。你能多说一点吗?

当然-我正在为Git(如果愿意的话,为libgit ++)编写一个C ++库,并且遇到了与此类似的问题:我需要能够打开大文件(大型文件)并且不具备性能。 (与一样std::fstream)。

Boost::Iostreams已经有一个mapping_file源,但是问题在于它正在mmapping整个文件,这将您限制为2 ^(wordsize)。在32位计算机上,4GB不够大。期望.packGit中的文件变得比原来大很多是不合理的,因此我需要不依靠常规文件I / O而分块读取文件。在的掩盖下Boost::Iostreams,我实现了一个Source,它或多或少是std::streambuf和之间相互作用的另一种观点std::istream。您也可以尝试类似的方法,只需继承std::filebufmapped_filebuf和,然后继承std::fstream到即可a mapped_fstream。两者之间的相互作用很难正确处理。 Boost::Iostreams 已经为您完成了一些工作,并且还为过滤器和链提供了挂钩,因此我认为以这种方式实现它会更有用。


3
RE:Windows上的映射文件缓存。确实:启用文件缓冲后,内核内存会在内部映射您正在读取的文件,读入该缓冲区并将其复制回您的进程中。就像您自己进行内存映射一样,除了额外的复制步骤。
克里斯·史密斯,

6
我不愿意不同意接受的答案,但是我相信这个答案是错误的。我遵循了您的建议,并在64位Linux机器上尝试了代码,而mmap()并不比STL实现快。而且,从理论上讲,我不希望'mmap()'更快(或更慢)。
蒂姆·库珀

3
@Tim Cooper:您可能会发现此线程(markmail.org/message/…)很有趣。请注意两件事:mmap在Linux中没有得到适当的优化,并且还需要在测试中使用madvise以获得最佳结果。
本·科林斯

9
亲爱的本:我已经阅读了该链接。如果在Linux上'mmap()'速度不快,而在Windows上MapViewOfFile()速度不快,那么您能否声称“ mmap速度更快”?另外,出于理论上的原因,我相信mmap()对于顺序读取而言不会更快-您有相反的解释吗?
蒂姆·库珀

11
Ben,为什么mmap()每次都要打扰一个页面?如果a size_t有足够的空间来容纳文件的大小(很可能在64位系统上),则只需mmap()一次调用即可整个文件。
史蒂夫·埃默森,

39

这里已经有很多很好的答案,涵盖了很多要点,所以我只添加几个我没有直接在上面解决的问题。也就是说,此答案不应被认为是全面的优缺点,而应视为此处其他答案的附录。

mmap看起来像魔术

以文件已经被完全缓存1为基线2的情况下mmap看起来很像魔术

  1. mmap 仅需要1个系统调用即可(可能)映射整个文件,此后不再需要任何系统调用。
  2. mmap 不需要将文件数据从内核复制到用户空间。
  3. mmap允许您“作为内存”访问文件,包括使用您可以针对内存执行的任何高级技巧对其进行处理,例如编译器自动矢量化,SIMD内在函数,预取,优化的内存解析例程,OpenMP等。

如果文件已经在高速缓存中,则似乎无法克服:您只是直接访问内核页面高速缓存作为内存,而且它的速度不能超过此速度。

好吧,可以。

mmap实际上不是魔术,因为...

mmap仍可以按页面工作

mmapvs的主要隐藏成本read(2)(实际上是与读取块相对应的OS级syscall )是,mmap您需要为用户空间中的每个4K页面做一些“工作”,即使它可能被用户空间隐藏了。页面故障机制。

例如,仅mmap整个文件的典型实现将需要进行故障处理,因此100 GB / 4K = 2500万个故障才能读取100 GB的文件。现在,这些将是次要的错误,但是250亿页的错误仍然不会很快。在最佳情况下,一次小故障的成本可能约为100纳米。

mmap严重依赖TLB性能

现在,你可以传递MAP_POPULATEmmap告诉它在返回前设置所有的页表,所以在访问它不应该有页面错误。现在,这有一个小问题,它也将整个文件读入RAM,如果您尝试映射100GB的文件,该文件将被炸毁-但现在我们忽略它3。内核需要完成每页的工作才能设置这些页表(显示为内核时间)。这最终成为该mmap方法的主要成本,并且与文件大小成正比(即,随着文件大小的增加,它并不会变得相对不那么重要)4

最后,即使在用户空间中访问,这种映射也不是完全免费的(与并非源自基于文件的大内存缓冲区相比mmap)-即使建立了页表,每次对新页的访问都将到达,从概念上讲,会导致TLB错过。由于mmap写入文件意味着使用页面缓存及其4K页面,因此对于100GB的文件,您再次要花费2500万次。

现在,这些TLB缺失的实际成本在很大程度上至少取决于硬件的以下方面:(a)您拥有多少4K TLB实体以及其余的转换缓存如何工作(b)硬件预取处理得如何好使用TLB-例如,预取能否触发页面浏览?(c)分页浏览硬件的速度和并行度。在现代高端x86 Intel处理器上,页面浏览硬件通常非常强大:至少有2个并行页面浏览器,页面浏览可以与连续执行同时发生,并且硬件预取可以触发页面浏览。因此,TLB 对流读取负载的影响非常小-且无论页面大小如何,这种负载通常都将以类似的方式执行。但是,其他硬件通常要差得多!

read()避免了这些陷阱

read()系统调用,这是一般伏于“块读”呼叫类型提供例如,在C,C ++和其他语言有一个主要的缺点,每个人都充分认识到:

  • 每次read()调用N个字节必须将N个字节从内核复制到用户空间。

另一方面,它避免了上述大部分成本-您无需将2500万个4K页面映射到用户空间。通常,您可以malloc在用户空间中使用单个缓冲区或小缓冲区,然后将其重复用于所有read调用。在内核方面,4K页或TLB丢失几乎没有问题,因为通常使用几个非常大的页(例如,x86上的1 GB页)线性映射所有RAM,因此覆盖了页缓存中的基础页在内核空间中非常有效。

因此,基本上,您可以通过下面的比较来确定一次读取大文件的速度更快:

这种mmap方法隐含的额外的每页工作是否比使用隐含的将文件内容从内核复制到用户空间的每字节工作更昂贵read()

在许多系统上,它们实际上是近似平衡的。请注意,每个扩展的硬件和操作系统堆栈的属性完全不同。

特别是在以下情况下,该mmap方法变得相对较快:

  • 该操作系统具有快速的轻微故障处理功能,尤其是诸如故障排除之类的轻微故障批量优化。
  • OS具有良好的MAP_POPULATE实现,可以在例如基础页面在物理内存中连续的情况下有效处理大型地图。
  • 硬件具有强大的页面翻译性能,例如大型TLB,快速的第二级TLB,快速和并行的页面遍历器,与翻译的良好预取交互等。

...虽然在以下情况下该read()方法变得相对较快:

  • read()系统调用具有良好的复制性能。例如,copy_to_user内核方面的良好性能。
  • 内核具有一种有效的(相对于用户态)映射内存的方式,例如,仅使用几个带有硬件支持的大页面。
  • 内核具有快速的系统调用,并且可以在整个系统调用之间保留内核TLB条目。

上面的硬件因素在不同平台之间,甚至在同一家族中(例如,在x86代之内,尤其是细分市场),以及在不同体系结构(例如,ARM与x86与PPC)之间,差异都很大

操作系统因素也不断变化,双方的各种改进都导致一种方法或另一种方法的相对速度大幅提高。最近的列表包括:

  • 如上所述,增加了故障排除功能,对mmap没有的情况确实有帮助MAP_POPULATE
  • 在中添加快速路径copy_to_user方法arch/x86/lib/copy_user_64.S(例如,REP MOVQ何时快速使用),确实可以解决问题read()

幽灵和崩溃后更新

Spectre和Meltdown漏洞的缓解措施大大增加了系统调用的成本。在我测量的系统上,“不执行任何操作”系统调用(除了该调用完成的任何实际工作之外,这是系统调用的纯开销的估计)的成本大约为100 ns现代Linux系统大约需要700 ns。此外,根据您的系统,由于需要重新加载TLB条目,专门用于Meltdown 的页表隔离修复程序可能会具有其他下游影响,除了直接的系统调用成本之外。

read()基于方法的方法相比,这对于基于方法的方法都是相对不利的mmap,因为read()方法必须针对每个“缓冲区大小”的数据进行一次系统调用。您不能随意增加缓冲区大小以分摊此成本,因为使用大型缓冲区通常会更糟,因为您超过了L1的大小,因此不断遭受高速缓存未命中的困扰。

另一方面,使用mmap,您可以在一个较大的内存区域中进行映射,MAP_POPULATE并可以高效地对其进行访问,而只需一个系统调用即可。


1这种或多或少的情况还包括文件没有被完全缓存到开始的情况,但是预读操作系统足以使文件看起来像这样(例如,页面通常在您存储时被缓存)。想要它)。但是,这是一个微妙的问题,因为预调用的工作方式在mmapread调用之间通常大不相同,并且可以通过2中所述的“建议”调用进一步调整。

2 ...因为如果缓存文件,那么您的行为将完全由IO问题决定,包括您对底层硬件的访问模式有多同情-您应尽一切努力确保此类访问具有同情心例如,通过使用madvisefadvise调用(以及可以对应用程序级别进行任何更改以改善访问模式)。

3例如,您可以mmap通过在较小尺寸(例如100 MB)的窗口中依次打开来解决此问题。

4实际上,事实证明,该MAP_POPULATE方法(至少是某些硬件/操作系统组合中的一种)仅比不使用它要快一点,这可能是因为内核正在使用故障排除方法 -因此实际的次要故障数减少了16倍或者。


4
感谢您为这个复杂的问题提供更细微的回答。对于大多数人来说,mmap似乎更快,而实际上却并非如此。在我的实验中,使用pread()随机访问具有内存索引的大型100GB数据库,即使我为数百万次访问中的每一个都分配了一个缓冲区,速度也更快。似乎业内的一群人也观察到了同样的情况
Caetano Sauer'5

5
是的,这在很大程度上取决于场景。如果读取的数据足够小,并且随着时间的流逝,您倾向于重复读取相同的字节,那么mmap它将具有无法克服的优势,因为它避免了固定的内核调用开销。另一方面,这mmap也会增加TLB压力,并且实际上会使“热身”阶段变慢,在“热身”阶段中,在当前进程中首次读取字节(尽管它们仍在页面页面中),因为这样做可能会比例read如“故障处理”相邻页面还要多的工作……而对于相同的应用程序,“热身”就很重要!@CaetanoSauer
BeeOnRope'5

我认为您在说“ ...但是250亿个页面错误仍然不会变得非常快...”的地方应该显示为“ ...但是2500 万个页面错误仍然不会变得非常快...”。 。我不是100%积极的人,所以这就是为什么我不直接编辑。
Ton van den Heuvel

7

抱歉,本·科林斯(Ben Collins)丢失了滑动窗口的mmap源代码。在Boost中拥有它真是太好了。

是的,映射文件要快得多。本质上,您是在使用OS虚拟内存子系统来将内存与磁盘关联,反之亦然。这样考虑:如果OS内核开发人员可以使其更快,他们就会这样做。因为这样做可以使几乎所有事情变得更快:数据库,启动时间,程序加载时间等等。

滑动窗口方法实际上并不难,因为可以一次映射多个连续页面。因此,记录的大小无关紧要,只要任何单个记录中的最大记录可以放入内存即可。重要的是管理簿记。

如果记录不是从getpagesize()边界开始的,则映射必须从上一页开始。映射区域的长度从记录的第一个字节(必要时向下舍入到getpagesize()的最接近倍数)到记录的最后一个字节(舍入到getpagesize()的最接近倍数)。处理完记录后,可以取消对其的映射(),然后移至下一条。

在Windows下,也可以使用CreateFileMapping()和MapViewOfFile()(和GetSystemInfo()来获得SYSTEM_INFO.dwAllocationGranularity ---而不是SYSTEM_INFO.dwPageSize),在Windows上也可以正常工作。


我只是在Google上搜索,发现了关于dwAllocationGranularity的小片段-我正在使用dwPageSize,并且一切都崩溃了。谢谢!
wickedchicken

4

mmap应该更快,但我不知道多少。这在很大程度上取决于您的代码。如果您使用mmap,最好一次映射整个文件,这将使您的生活变得更加轻松。一个潜在的问题是,如果您的文件大于4GB(或者实际上限制较低,通常为2GB),则需要64位体系结构。因此,如果您使用的是32位环境,则可能不想使用它。

话虽如此,可能会有更好的途径来提高性能。您说输入文件被扫描了很多次,如果您可以一次性读取并完成它,则可能会更快。


3

也许您应该对文件进行预处理,所以每个记录都在一个单独的文件中(或者至少每个文件都是可映射的大小)。

还可以在移至下一条记录之前对每条记录执行所有处理步骤吗?也许这样可以避免一些IO开销?


3

我同意mmap的文件I / O会更快,但是在对代码进行基准测试时,是否应该对反例进行一些优化?

本·科林斯写道:

char data[0x1000];
std::ifstream in("file.bin");

while (in)
{
    in.read(data, 0x1000);
    // do something with data 
}

我建议也尝试:

char data[0x1000];
std::ifstream iifle( "file.bin");
std::istream  in( ifile.rdbuf() );

while( in )
{
    in.read( data, 0x1000);
    // do something with data
}

除此之外,您还可以尝试使缓冲区的大小与虚拟内存的一页大小相同,以防万一0x1000不是您计算机上虚拟内存的一页大小...胜,但这应该使事情变得更紧密。


2

在我看来,使用mmap()“只是”使开发人员不必编写自己的缓存代码。在一个简单的“一次读取文件一次”的情况下,这并不困难(尽管mlbrock指出您仍将内存副本保存到进程空间中),但是如果要在文件中来回移动或跳过诸如此类,我相信内核开发人员可能比我能做的更好。


1
与内核相比,您很有可能在缓存特定于应用程序的数据方面做得更好,后者以非常盲目的方式对页面大小的块进行操作(例如,它仅使用简单的伪LRU方案来决定要驱逐哪些页面) )-虽然您可能对正确的缓存粒度了解很多,并且对将来的访问模式也有了很好的了解。mmap缓存的真正好处是,您只需重复使用已经存在的现有页面缓存,即可免费获得该内存,并且该内存也可以在各个进程之间共享。
BeeOnRope

2

我记得几年前将包含树结构的巨大文件映射到内存中。与普通的反序列化相比,它的速度让我感到惊讶,它需要大量的内存工作,例如分配树节点和设置指针。因此,实际上我是在将对mmap的单个调用(或Windows上的对应调用)与对运算符new和构造函数调用的许多(MANY)调用进行比较。对于此类任务,与反序列化相比,mmap是无与伦比的。当然,应该为此研究提升可重定位指针。


这听起来更像是灾难的秘诀。如果对象布局发生变化该怎么办?如果您具有虚函数,则所有vftbl指针都可能是错误的。您如何控制文件映射到的位置?您可以给它一个地址,但这只是一个提示,内核可能会选择另一个基地址。
詹斯

当您具有稳定且明确定义的树布局时,此方法非常适用。然后,您可以将所有内容强制转换为相关的结构,并通过每次添加“ mmap起始地址”的偏移量来跟踪内部文件指针。这与使用索引节点和目录树的文件系统非常相似
Mike76'1

1

这听起来像是多线程的好用例……我想您可以很容易地将一个线程设置为读取数据,而其他线程则对其进行处理。这可能是显着提高感知性能的一种方式。只是一个想法。


是的 我一直在考虑这一点,可能会在以后的版本中尝试一下。我唯一的保留意见是处理时间远远短于I / O延迟,因此可能没有太多好处。
jbl

1

我认为mmap的最大优点是可以异步读取:

    addr1 = NULL;
    while( size_left > 0 ) {
        r = min(MMAP_SIZE, size_left);
        addr2 = mmap(NULL, r,
            PROT_READ, MAP_FLAGS,
            0, pos);
        if (addr1 != NULL)
        {
            /* process mmap from prev cycle */
            feed_data(ctx, addr1, MMAP_SIZE);
            munmap(addr1, MMAP_SIZE);
        }
        addr1 = addr2;
        size_left -= r;
        pos += r;
    }
    feed_data(ctx, addr1, r);
    munmap(addr1, r);

问题是我找不到正确的MAP_FLAGS来提示该内存应从文件asap同步。我希望MAP_POPULATE为mmap提供正确的提示(即,它不会在调用返回之前尝试加载所有内容,但会通过feed_data异步进行加载)。至少它使用此标志提供了更好的结果,即使手册指出自2.6.23起没有MAP_PRIVATE也不执行任何操作。


1
您想要posix_madvise带有WILLNEED标志以预先填充惰性提示。
ShadowRanger

@ShadowRanger,听起来很合理。尽管我将更新手册页以明确说明这posix_madvise是异步调用。mlock对于那些想要等到整个内存区域可用而没有页面错误的人来说,这也将是很好的参考。
y
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.