【问题标题】:Why does the VS2008 std::string.erase() move its buffer?为什么 VS2008 std::string.erase() 移动它的缓冲区?
【发布时间】:2011-11-17 17:49:17
【问题描述】:

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

所以我拥有的是这样的:

 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++ 标准库标签是什么?

【问题讨论】:

  • STL(有些错误,但被接受)、std 或 stdlib。
  • 啊,我试过 C++-std-library 并没有解决。所以习惯了命名空间我忘了它只是一个缩写:)

标签: c++ visual-studio-2008 c++-standard-library


【解决方案1】:

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

Connect Bug: "std::string::erase is stupidly slow when erasing to the end, which impacts std::string::resize"

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

【讨论】:

  • +1 用于查找编译器错误。没有多少人能做到这一点。
  • 不是真正的编译器,而是 STL 实现“错误”。我只是想指出回答 MS 家伙的首字母 - 多么巧合:D
  • +1 不错的报告,本。显然,我在您最初报告时看到了它,因为我已经在 Connect 上投票了。 :-P
  • 谢谢。通过这些响应提出的见解并对其进行更多研究,很明显,在我的代码的情况下,移动永远不会发生,所以正如你所说的库实现不是最佳的,但在这种情况下还不足以让我担心.
【解决方案2】:

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 不买你 很多。在您阅读了文件中的几行之后,您的 无论如何字符串不会增长太多,所以会很少 在前几行之后重新分配。另一个案例 过早优化?)

【讨论】:

  • 我认为现在还为时过早。它不会增加任何混乱,而且确实有速度优势。
  • @MooingDuck 我想你说的是reserve:它对性能没有任何可衡量的影响,而且它添加了不必要的代码行。
  • 我说的是reserve是的。看来你和我对“过早”优化的定义不同,这很好。辩论还不够重要。
  • 我现在看到了我忽略的显而易见的事情,即当只擦除字符串的一部分时,擦除确实必须将其余的东西向下移动,但是正如 Ben Voigt 指出的那样,在擦除整个字符串的常见情况下,那是相当多的代码完全没有目的执行,在这种常见情况下,避免调用的检查将远不及执行所有无意义的代码的成本。不过谢谢你的其他提示,好吧,在我被我在擦除中发现的东西所迷惑之前,我还没有真正完成我的循环整理:)
  • 关于储备我倾向于同意从技术上讲这可能是一个过早的优化。但是,我认为在进入循环之前选择合理缓冲区大小的特定情况是一种足够标准的模式,可以说它只是一种好的形式,而不是优化本身。这可能只是一个意见问题。
【解决方案3】:

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

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]
}

【讨论】:

  • 我实际上的意思是去一个更低的级别并使用 Win32 CreateFile 将整个文件读入原始内存缓冲区,我什至考虑这样做的唯一原因是因为这就是所有其他 IO 的方式在我目前正在开发的应用程序中完成:(在您的示例中,即使您引用了 istringstream.str() 它仍然是整个受控序列的副本,对吗?在这种情况下,因为您已经复制了整个文件这真的比逐行读取文件更高效吗?
  • @Neutrino:它可能是也可能不是副本。以我的经验,它通常确实比逐行阅读具有更好的性能。您可能想查看previous answer 进行一些基准测试和一些性能更好的代码。至少如果有记忆的话,逐行读取似乎与那里使用的迭代器的速度大致相同。
猜你喜欢
  • 2012-10-11
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-05-10
  • 2018-07-13
  • 2020-04-06
  • 1970-01-01
相关资源
最近更新 更多