5

我想逐行读取文件并捕获一个特定的输入行。为了获得最佳性能,我可以通过读取整个文件并使用指针迭代其内容以低级别的方式执行此操作,但是此代码对性能不是至关重要的,因此我希望使用更具可读性和类型安全的标准库样式实现。

所以我所拥有的是:

 std::string line;
 line.reserve(1024);
 std::ifstream file(filePath);
 while(file)
 {
    std::getline(file, line);
    if(line.substr(0, 8) == "Whatever")
    {
        // Do something ...
    }
 }

虽然这不是性能关键代码,但我在解析操作之前调用了 line.reserve(1024) 以防止在读入较大的行时对字符串进行多次重新分配。

在 std::getline 中,字符串在将每一行的字符添加到它之前被擦除。我单步执行了这段代码,以使自己确信内存没有在每次迭代中重新分配,我发现这让我大吃一惊。

在 string::erase 的深处,而不是仅仅将其大小变量重置为零,它实际上正在做的是调用 memmove_s 并使用指针值来覆盖缓冲区的已使用部分,而缓冲区的未使用部分紧随其后,除了 memmove_s 正在以零计数参数调用,即请求移动零字节。

问题:

为什么我希望在我可爱的循环中间产生一个库函数调用的开销,尤其是一个被调用根本不做任何事情的循环?

我自己还没有把它拆开,但是在什么情况下这个调用实际上不会做任何事情,但实际上会开始移动大块缓冲区?

它为什么要这样做呢?

奖励问题:C++ 标准库标签是什么?

4

3 回答 3

11

这是我在一年前报告的一个已知问题,要利用修复,您必须升级到编译器的未来版本。

连接Bug:“std::string::erase擦除到最后速度很慢,影响std::string::resize

该标准没有说明任何std::string函数的复杂性,除了swap.

于 2011-11-17T18:04:00.200 回答
3

std::string::clear()是根据 定义的std::string::erase(),并且std::string::erase()确实必须将所有字符移动到被擦除的块之后。那么为什么不应该调用一个标准函数来这样做呢?如果您有一些分析器输出证明这是一个瓶颈,那么也许您可以抱怨它,但否则,坦率地说,我看不出它有什么不同。(避免通话所需的逻辑最终可能比通话花费更多。)

此外,您不会getline在使用前检查调用结果。你的循环应该是这样的:

while ( std::getline( file, line ) ) {
    //  ...
}

而且,如果您非常担心性能,那么创建一个子字符串(一个新的 std::string)只是为了进行比较比调用memmove_s. 像这样的东西有什么问题:

static std::string const target( "Whatever" );
if ( line.size() >= target.size()
        && std::equal( target.begin(), target().end(), line.being() ) ) {
    //  ...
}

我认为这是确定字符串是否以特定值开头的最惯用的方法。

(我可能会根据经验补充一点,reserve这里也不会给您带来太多好处。在您阅读了文件中的几行之后,您的字符串无论如何都不会增长太多,因此重新分配的次数很少在前几行之后。另一种过早优化的情况?)

于 2011-11-17T18:08:33.950 回答
0

在这种情况下,我认为您提到的读取整个文件并迭代结果的想法实际上可能只是简单的代码。您只是将:“读取行,检查前缀,处理”更改为“读取文件,扫描前缀,处理”:

size_t not_found = std::string::npos;
std::istringstream buffer;

buffer << file.rdbuf();

std::string &data = buffer.str();

char const target[] = "\nWhatever";
size_t len = sizeof(target)-1;

for (size_t pos=0; not_found!=(pos=data.find(target, pos)); pos+=len)
{
    // process relevant line starting at contents[pos+1]
}
于 2011-11-17T19:17:53.600 回答