我想写日志文件,非结构化格式(一次一行),使用mmap
(速度)。 什么是最好的程序? 我打开空文件, truncate
到1页面大小(写空string来调整文件大小?),然后mmap
– 并重复当mmaped区域已满?
我通常使用mmap
编写固定大小的结构,通常每次只有一个页面,但是这是为了使用mmap编写日志文件(从0.5到10 Gb的任何地方),但不知道在第一个mmaped区域填充之后最佳做法是什么 – munmap
,调整文件truncate
和mmap
下一页?
在将日志写入内存区域时,我会跟踪大小和msync
,一旦到达映射内存区域的末尾,什么是正确的处理?
比方说,我不需要回头或覆盖现有的数据,所以我只写入新的数据文件。
Q1:当我到达映射区域的末尾时,是否需要进行映射, ftruncate
文件是否需要调整其他页面大小,然后mmap
下一页?
问题2:有没有一种标准的方法来抢先预备下一页,并准备下一页? 当我们接近映射区域的末端时,在另一个线程上执行此操作?
问题3:我为顺序访问而madvise
吗?
这是实时数据处理与要求保持日志文件 – 目前我只是写入文件。 日志文件是非结构化的,文本格式,基于行。
这是为Linux / c + + / c可选在Mac上testing(所以没有重映射[?])。
任何链接/指向最佳实践的赞赏。
我写了关于fwrite VS mmap( “测量传统I / O和内存映射文件之间的性能折衷的实验” )的比较论文。 首先,为了编写,你不必去寻找内存映射文件,尤其是大文件。 fwrite
是完全正常的,几乎总是会胜过使用mmap
方法。 mmap
将为您提供最高的并行数据读取性能。 对于顺序数据写你的真正的限制与fwrite
是你的硬件。
在我的示例中, remapSize
是文件的初始大小以及每次重新映射时文件增加的大小。 fileSize
跟踪文件的大小, mappedSpace
代表当前mmap(它的长度)的大小, alreadyWrittenBytes
是已经写入文件的字节。
以下是初始化的例子:
void init() { fileDescriptor = open(outputPath, O_RDWR | O_CREAT | O_TRUNC, (mode_t) 0600); // Open file result = ftruncate(fileDescriptor, remapSize); // Init size fsync(fileDescriptor); // Flush memoryMappedFile = (char*) mmap64(0, remapSize, PROT_WRITE, MAP_SHARED, fileDescriptor, 0); // Create mmap fileSize = remapSize; // Store mapped size mappedSpace = remapSize; // Store mapped size }
广告Q1:
我使用了“Unmap-Remap”机制。
不映射
msync
) 这可能看起来如下:
void unmap() { msync(memoryMappedFile, mappedSpace, MS_SYNC); // Flush munmap(memoryMappedFile, mappedSpace) }
对于重映射 ,您可以选择重新映射整个文件或只重新附加的部分。
基本上重新映射
完整重映射的示例实现:
void fullRemap() { ftruncate(fileDescriptor, mappedSpace + remapSize); // Make file bigger fsync(fileDescriptor); // Flush file memoryMappedFile = (char*) mmap64(0, mappedSpace + remapSize, PROT_WRITE, MAP_SHARED, fileDescriptor, 0); // Create new mapping on the bigger file fileSize += reampSize; mappedSpace += remapSize; // Set mappedSpace to new size }
小重映射的示例实现:
void smallRemap() { ftruncate(fileDescriptor, fileSize + remapSize); // Make file bigger fsync(fileDescriptor); // Flush file remapAt = alreadyWrittenBytes % pageSize == 0 ? alreadyWrittenBytes : alreadyWrittenBytes - (alreadyWrittenBytes % pageSize); // Adjust remap location to pagesize memoryMappedFile = (char*) mmap64(0, fileSize + remapSize - remapAt, PROT_WRITE, MAP_SHARED, fileDescriptor, remapAt); // Create memory-map fileSize += remapSize; mappedSpace = fileSize - remapAt; }
这里有一个mremap function
,但是它声明
这个调用是特定于Linux的,不应该用在可移植的程序中。
广告Q2:
我不确定我是否明白这一点。 如果你想告诉内核“现在加载下一页”,那么不,这是不可能的(至少据我所知)。 但请参阅广告Q3如何建议内核。
广告Q3:
你可以使用madvise
和MADV_SEQUENTIAL
标志,但是请记住,这并不强制内核读取,而只是提供建议。
摘录形式的男人 :
这可能会导致内核积极预读
个人结论:
不要使用mmap
进行顺序数据写入。 它只会导致更多的开销,并会导致比使用fwrite
的简单写入算法更多的“非自然”代码。
使用mmap
进行大文件的随机读取读取。
这也是我在论文中获得的结果。 我无法通过使用mmap
进行顺序写入来实现任何加速,事实上,对于此目的而言总是较慢。
使用mmap(速度)。 什么是最好的程序?
不要使用mmap
,请使用write
。 认真。 为什么人们总是认为mmap
会以某种方式神奇地加快速度?
创建一个mmap
并不便宜,这些页表不会自己填充。 当你想追加到一个文件,你必须
对于mmap有一些很好的用法,例如在大数据集中进行随机访问读取或者从同一数据集中进行反复读取时。
为了进一步阐述,我会提到Linus Torvalds本人:
http://lkml.iu.edu/hypermail/linux/kernel/0004.0/0728.html
保罗·巴顿 – 戴维斯(Paul Barton-Davis)在<200004042249.SAA06325@op.net>中写道:>
我非常沮丧地发现,在我的系统上,只要读取解决方案,mmap / mlock方法花费了3倍的时间 。 在我看来,mmap / mlock应该至少和读取一样快。 评论被邀请。
人们喜欢使用mmap()和其他方式来使用页表来优化复制操作,有时这是值得的。
但是,使用虚拟内存映射玩游戏本身非常昂贵。 它有一些相当真实的缺点,人们往往忽视,因为记忆复制被认为是非常缓慢的,有时优化这个副本被视为一个明显的改进。
下降到mmap:
非常明显的设置和拆卸成本。 我的意思是明显的 。 这是像下面的表格来清理干净的一切。 这是保存所有映射列表的簿记。 这是在取消映射东西后需要的TLB刷新。
页面错误是昂贵的。 这就是地图填充的方式,而且速度很慢。
mmap的上层:
如果数据被一遍又一遍地重复使用(在一次映射操作中),或者只要映射某些内容就可以避免很多其他逻辑,mmap()就是切片面包以来最伟大的事情。 这可能是一个文件,你多次去(一个可执行文件的二进制图像是在这里明显的例子 – 代码跳转到所有地方),或者一个设置,它只是方便地绘制整个事情,而不考虑mmap()刚刚赢得的实际使用模式。 您可能有随机访问模式,并使用mmap()作为跟踪您实际需要的数据的方式。
如果数据很大,mmap()是让系统知道数据集可以做什么的好方法。 内存可能会因为内存压力而忘记页面,迫使系统将页面内容分页,然后再自动重新获取它们。
而自动共享显然是这样的一个例子。
但是你的测试套件(只复制一次数据)对于mmap()来说可能是非常重要的。
莱纳斯