为什么逐个读取一个字符比遍历整个文件字符串更快?
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 接口。
我有一个词法分析器,它逐个字符地处理文件,寻找标记。我为 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 接口。