mmap slower than getline?

mmap slower than getline?

本文关键字:getline than slower mmap      更新时间:2023-10-16

我面临着一行一行地读/写文件(以gb为单位)的挑战。

阅读了许多论坛条目和网站(包括一堆SO), mmap被建议作为读取/写入文件的最快选项。但是,当我同时使用readline和mmap技术实现代码时,mmap是两者中较慢的。阅读和写作都是如此。我一直在测试大约600mb大的文件。

我的实现逐行解析,然后对行进行标记。我将只显示文件输入。

下面是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不适合我的应用程序?

感谢您的任何评论或帮助!

mmap的真正功能是能够自由地在文件中查找,直接将其内容用作指针,并避免将数据从内核缓存内存复制到用户空间的开销。但是,您的代码示例并没有利用这一点。

在循环中,每次扫描一个字符,附加到stringstreamstringstream不知道字符串有多长,因此必须在这个过程中重新分配几次。此时,您已经消除了使用mmap带来的任何性能提升-即使是标准的getline实现也避免了多次重新分配(通过使用128字节的堆栈上缓冲区,在GNU c++实现中)。

如果你想充分发挥mmap的作用:

    不要复制你的字符串。在所有。相反,将指针复制到mmap缓冲区中。
  • 使用内置函数strnchrmemchr查找换行符;它们利用手卷汇编器和其他优化来比大多数开编码搜索循环运行得更快。

告诉你使用mmap的人不太了解现代机器。

mmap的性能优势完全是一个神话。用Linus Torvalds的话来说:

是的,内存是"慢"的,但是该死的,mmap()也是。

mmap的问题是,每次您第一次触摸映射区域中的页面时,它都会进入内核并实际将该页映射到您的地址空间,从而破坏TLB。

尝试一个简单的基准测试,使用read一次读取8K的大文件,然后再使用mmap。(反复使用相同的8K缓冲区。)您几乎肯定会发现read实际上更快

你的问题从来都不是从内核中获取数据;关键在于你如何处理之后的数据。尽量减少你每次所做的工作;只需扫描以找到换行符,然后在块上执行单个操作。就我个人而言,我会回到read实现,使用(和重用)适合L1缓存(8K左右)的缓冲区。

或者至少,我会尝试一个简单的readmmap基准测试,看看哪个在您的平台上实际上更快。

(更新)

我发现了Torvalds先生的几组评论:

http://lkml.iu.edu/hypermail/linux/kernel/0004.0/0728.htmlhttp://lkml.iu.edu/hypermail/linux/kernel/0004.0/0775.html

摘要:

最重要的是,你仍然有实际的CPU TLB丢失成本等。如果你只是重复阅读同一领域,通常可以避免这些问题而不是过分聪明的内存管理只是避免复制。

memcpy()(即"read()"在这种情况下)是总是将更快很多情况下,只是因为它避免了所有额外的复杂性。而在其他情况下,Mmap()会更快。

根据我的经验,顺序读取和处理大文件是使用(和重用)中等大小的缓冲区并使用read/write的"许多情况"之一,其性能明显优于mmap

您可以使用memchr来查找行结尾。这将比每次添加一个字符到stringstream要快得多。

您正在使用stringstream s来存储您识别的行。这与getline实现没有可比性,stringstream本身增加了开销。正如其他人建议的那样,您可以将字符串的开头存储为char*,也可以存储为行长度(或指向行尾的指针)。read的主体类似于:

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)。