为什么逐个读取一个字符比遍历整个文件字符串更快?

Why is reading char by char faster than iterating over whole file string?

我有一个词法分析器,它逐个字符地处理文件,寻找标记。我为 NextChar() 尝试了两种方法,第一种直接从 ifstream 读取到 ifstream::get(ch),第二种将整个文件加载到 std::stringstream 以避免磁盘 I/O开销。

get() 方法:

inline void Scanner::NextChar()
{
    inputStream.get(unscannedChar);
    currentCol++;

    while (unscannedChar == ' ')
    {
        inputStream.get(unscannedChar);
        currentCol++;
    }

    if (inputStream.eof()) {
        unscannedChar = std::char_traits<char>::eof();
    }

}

stringstream方法: 虽然加载 the file into stringstream 不需要时间,但索引速度非常慢。

inline void Scanner::NextChar()
{
    unscannedChar = buffer.str()[counter++];
    currentCol++;

    while (unscannedChar == ' ')
    {
        unscannedChar = buffer.str()[counter++];
        currentCol++;
    }

    
    if (counter > buffer.str().size())
    {
        unscannedChar = std::char_traits<char>::eof();
    }

}

我原以为第二种方法会快得多,因为它是在内存中而不是磁盘上迭代字符,但我错了,这里是我的一些测试:

| tokens    | ifstream::get()   | stringstream::str()[]     |
|--------   |-----------------  |-----------------------    |
| 5         | 0.001 (sec)       | 0.001 (sec)               |
| 800       | 0.002 (sec)       | 0.295 (sec)               |
| 21000     | 0.044 (sec)       | 693.403 (sec)             |    

NextChar() 对我的项目非常重要,我需要尽快完成它,我很乐意解释为什么我有以前的结果?

std::ifstream已经在做自己的内部缓冲了,所以不会每次调用get(ch)都要出去等待硬盘响应; 99.99% 的情况下,它已经在其内部读取缓冲区中提供了您的下一个字符,只需进行一个字节的复制即可将其交给您的代码。

鉴于此,将整个文件复制到您自己的单独 RAM 缓冲区中不会获得额外的加速;事实上,这样做可能会使事情变得更慢,因为这意味着在整个文件被读入 RAM 之前你不能开始解析数据(而 ifstream 的较小的预读缓冲区,你的代码可以加载文件的第一部分后立即开始解析字符,解析可以在某种程度上与之后的磁盘读取并行进行)

最重要的是,stringstream::str() 会在您每次调用它时按值返回一个 string 对象,如果返回的 string 很大,这可能会非常昂贵。 (即,您正在为您解析的每个字符制作文件内容的内存副本,然后将其丢弃!)

根据我的经验,stringstream 很慢。参见示例:

https://github.com/TheNitesWhoSay/RareCpp/issues/28

所以我从不使用它。 如果性能很重要,可以考虑 flex 和 bison。

https://en.wikipedia.org/wiki/GNU_Bison

恕我直言,对于简单格式,最快的解析方法是使用 C 接口。