通过istringstream进行C++字符串分词的性能开销

6
我想知道的是,什么是性能开销。
string line, word;
while (std::getline(cin, line))
{
    istringstream istream(line);
    while (istream >> word)
        // parse word here
}

我认为这是标准的输入分词方法。 具体来说:
每一行都会通过getline,然后是istream构造函数,最后是operator>>用于每个单词的拷贝三次吗?
频繁的istream构造和销毁会有问题吗?如果我在外部while循环之前定义istream,那么等效的实现是什么?
谢谢!
更新:
一个等价的实现
string line, word;
stringstream stream;
while (std::getline(cin, line))
{
    stream.clear();
    stream << line;
    while (stream >> word)
        // parse word here
}

使用流作为本地堆栈,将行推入并弹出单词。这将消除先前版本中可能频繁的构造函数和析构函数调用,并利用流内部缓冲效应(这个观点正确吗?)。

替代方案可能是扩展std :: string以支持operator<<operator>>,或扩展iostream以支持类似locate_new_line的东西。 只是在这里进行头脑风暴


1
说实话,我认为你正在进行I/O操作,特别是与用户输入有关的操作,比你可能多次分配小数组要重要几个数量级。 - user1084944
2个回答

8

不幸的是,iostreams并不适用于性能密集型工作。问题不在于在内存中复制东西(复制字符串很快),而在于虚函数分派,可能涉及每个字符的多个间接函数调用。

至于你的关于复制的问题,是的,按照现有的写法,在初始化新的stringstream时会复制所有内容。(getline>>从流中复制字符到输出字符串,但这显然无法避免。)

使用C++11的move功能,您可以消除不必要的复制:

string line, word;
while (std::getline(cin, line)) // initialize line
{       // move data from line into istream (so it's no longer in line):
    istringstream istream( std::move( line ) );
    while (istream >> word)
        // parse word here
}

尽管如此,性能只有在测量工具告诉你它是问题时才是一个问题。Iostreams非常灵活和强大,filebuf基本上足够快,因此您可以原型化代码使其正常运行,然后优化瓶颈而无需重写所有内容。


你真的对basic_streambuf设计有所误解;仅当istringstream使用的流缓冲区完全未缓冲时,才会发生你所描述的情况。 - user1084944
@Hurkyl 如果存在编码转换(例如UTF-8),或者提取的字段很小(每个提取至少需要一个虚拟调用),那么filebuf是相对较快的。正如我在上一段中提到的那样,但实际上流提取会比你预期的更快成为瓶颈。 - Potatoswatter
我不能谈论区域设置,但我在小提取字段中没有看到虚拟调用。浏览文档,basic_istream 没有任何虚拟成员,而从 steambuf 中实际提取字符的操作将使用非虚拟函数(如 sbumpc)完成,这些函数只是指针算术和范围检查,并且只有在缓冲区为空等条件下才会调用虚拟函数。 - user1084944
@Hurkyl 提取相关的调用在本地化中。本地化特性定义数字格式等内容的格式。问题的很大一部分也在于使用该间接分支调用的函数通常也很慢。标准的本地化部分定义了许多功能,其中很多从未被使用过,我不知道是否有一个实际尝试优化“常见情况”的库。尝试在您的平台上对解析大型CSV文件进行分析。 - Potatoswatter
std::istringstream 构造函数仅接受 const std::string& 类型的字符串,因此 std::move 无法避免复制。参考文献 还明确指出:str: A string object, whose content is copied. - nspo

2
当你在一个块内定义变量时,它将被分配到堆栈上。当你离开该块时,它将从堆栈中弹出。使用这段代码,你会在堆栈上有很多操作。'word'也是如此。你可以使用指针并操作指针而不是变量。指针也存储在堆栈上,但它们指向的位置是堆内存中的一个位置。
这样的操作可能会增加制作变量、将其推送到堆栈上和再次从堆栈中弹出的开销。但是使用指针,您只需分配一次空间,然后在堆中使用已分配的空间。此外,指针可以比实际对象小得多,因此它们的分配速度会更快。
正如您所看到的,getLine()方法接受对line对象的引用(某种指针),使其能够在不再创建字符串对象的情况下使用它。
在您的代码中,lineword变量只被创建一次,然后使用它们的引用。每次迭代中唯一创建的对象是ss变量。如果您不想在每次迭代中制作它,可以在循环之前制作它,并使用其相关方法初始化它。您可以搜索以找到适当的方法来重新分配它,而不使用构造函数。
你可以这样做:
string line, word ;
istringstream ss ;
while (std::getline(cin, line))
{
    ss.clear() ;
    ss.str(line) ;
    while (ss >> word) {
        // parse word here
    }
}

你可以使用这个参考 istringstream。编辑:感谢@jrok的评论。是的,在分配新字符串之前,你应该清除错误标志。这是str()的参考istringstream::str

4
如果你想要重复使用这个流,就需要清除错误标志。 - jrok

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接