我面临着逐行读取/写入文件(在Gigs中)的挑战。
读取许多论坛条目和网站(包括一堆SO),mmap被认为是读/写文件的最快选项。但是,当我用readline和mmap技术实现我的代码时,mmap是两者中较慢的一个。这对于阅读和写作都是如此。我一直在测试大约600 MB的文件。
我的实现逐行解析,然后对行进行标记。我只会提供文件输入。
以下是 getline 实施:
void two(char* path) {
std::ios::sync_with_stdio(false);
ifstream pFile(path);
string mystring;
if (pFile.is_open()) {
while (getline(pFile,mystring)) {
// c style tokenizing
}
}
else perror("error opening file");
pFile.close();
}
这是 mmap :
void four(char* path) {
int fd;
char *map;
char *FILEPATH = path;
unsigned long FILESIZE;
// find file size
FILE* fp = fopen(FILEPATH, "r");
fseek(fp, 0, SEEK_END);
FILESIZE = ftell(fp);
fseek(fp, 0, SEEK_SET);
fclose(fp);
fd = open(FILEPATH, O_RDONLY);
map = (char *) mmap(0, FILESIZE, PROT_READ, MAP_SHARED, fd, 0);
/* Read the file char-by-char from the mmap
*/
char c;
stringstream ss;
for (long i = 0; i <= FILESIZE; ++i) {
c = map[i];
if (c != '\n') {
ss << c;
}
else {
// c style tokenizing
ss.str("");
}
}
if (munmap(map, FILESIZE) == -1) perror("Error un-mmapping the file");
close(fd);
}
为了简洁起见,我省略了很多错误检查。
我的mmap实现是否不正确,从而影响性能?也许mmap对我的应用来说不理想?
感谢您的任何意见或帮助!
答案 0 :(得分:11)
mmap的真正强大之处在于能够在文件中自由搜索,直接将其内容用作指针,并避免将数据从内核缓存内存复制到用户空间的开销。但是,您的代码示例没有利用此功能。
在循环中,您一次扫描缓冲区一个字符,并附加到stringstream
。 stringstream
不知道字符串有多长,因此必须在该过程中多次重新分配。此时,您已经消除了使用mmap
的任何性能提升 - 即使标准的getline实现也避免了多次重新分配(在GNU C ++实现中使用128字节的堆栈缓冲区)。
如果您想充分利用mmap:
strnchr
或memchr
等内置函数查找换行符;这些使用手动汇编程序和其他优化比大多数开放编码的搜索循环运行得更快。答案 1 :(得分:6)
告诉你使用mmap
的人并不太了解现代机器。
mmap
的性能优势是一个完整的神话。在words of Linus Torvalds:
是的,记忆是“慢”,但是该死,mmap()也是如此。
mmap
的问题在于,每当您第一次触摸映射区域中的页面时,它都会陷入内核并实际将页面映射到您的地址空间,从而对TLB造成严重破坏。 / p>
尝试使用read
一次读取8K大文件的简单基准,然后再使用mmap
。 (反复使用相同的8K缓冲区。)您几乎肯定会发现read
实际上更快。
你的问题绝不是从内核中获取数据;这是因为你在那之后如何处理数据。最大限度地减少您每次都在做的工作;只需扫描以找到换行符,然后对该块执行单个操作。就个人而言,我会回到read
实现,使用(并重新使用)适合L1缓存(8K左右)的缓冲区。
或者至少,我会尝试一个简单的read
与mmap
基准测试,看看哪个版本在您的平台上实际上更快。
[更新]
我找到了Torvalds先生的几套评论:
http://lkml.iu.edu/hypermail/linux/kernel/0004.0/0728.html http://lkml.iu.edu/hypermail/linux/kernel/0004.0/0775.html
摘要:
最重要的是,你仍然有实际的CPU TLB未命中成本等。 如果您只是重新读入同一区域,通常可以避免这种情况 而不是过于聪明的内存管理只是为了 避免复制。
memcpy()(即本例中的“read()”)总是总是更快 很多情况下,只是因为它避免了所有额外的复杂性。而 在其他情况下,mmap()会更快。
根据我的经验,按顺序读取和处理大文件是“很多情况”之一,其中使用(并重新使用)具有read
/ write
的适度大小缓冲区的效果明显更好比mmap
。
答案 2 :(得分:0)
您可以使用memchr
查找行结尾。它会比一次添加stringstream
个字符快得多。
答案 3 :(得分:0)
您正在使用stringstream
来存储您标识的行。这与getline实现不具有可比性,stringstream本身增加了开销。正如其他建议的那样,您可以将字符串的开头存储为char*
,也可以是行的长度(或指向行尾的指针)。阅读的主体将是这样的:
char* str_start = map;
char* str_end;
for (long i = 0; i <= FILESIZE; ++i) {
if (map[i] == '\n') {
str_end = map + i;
{
// C style tokenizing of the string str_start to str_end
// If you want, you can build a std::string like:
// std::string line(str_start,str_end);
// but note that this implies a memory copy.
}
str_start = map + i + 1;
}
}
另请注意,这样做效率更高,因为您不会处理每个字符中的任何内容(在您将该字符添加到stringstream
的版本中)。