【问题标题】:c++ overhead from string concatenation字符串连接的c ++开销
【发布时间】:2013-04-05 21:41:15
【问题描述】:

我正在从 ifstream 中读取随机 ascii 的文本文件。我需要能够将整个消息放入字符串类型以进行字符解析。我目前的解决方案有效,但我认为我正在通过使用等效的方法来谋杀更冗长的文件的处理时间:

std::string result;

for (std::string line; std::getline(std::cin, line); )
{
    result += line;
}

我担心与这样的连接字符串相关的开销(这种情况发生了几千次,消息长达数千个字符)。我过去几天一直在浏览不同的潜在解决方案,但没有什么是非常合适的......我不知道提前消息的长度,所以我不认为使用动态大小的字符数组是我的答案.

我通读了this SO thread,这听起来几乎适用,但仍然让我不确定;

有什么建议吗?

【问题讨论】:

  • 这是一个真正的问题吗?
  • 您可以使用streambuf iterators一次性构造字符串。
  • @mfontanini:不,他只是为了好玩而突然编造出来的。没有什么比他的时间更好的事了,就像你和我一样。
  • ifstream表示一个文件,一个文件表示你可以stat,你可以stat表示你知道它的大小;我错过了什么吗?
  • 好的,所以如果他不知道大小并且不想连接,为什么不简单地逐行解析呢?恕我直言,连接并不是一个有效的问题。

标签: c++ string optimization file-io


【解决方案1】:

真正的问题是您不提前知道完整大小,因此您无法适当地分配内存。我希望您获得的性能影响与此有关,而不是与 strings 的连接方式有关,因为它在标准库中有效地完成了。

因此,我建议您推迟串联,直到您知道最终 string 的完整大小。也就是说,您首先将所有字符串存储在一个大的 vector 中,如下所示:

using namespace std;
vector<string> allLines;
size_t totalSize = 0;
// If you can have access to the total size of the data you want
// to read (size of the input file, ...) then just initialize totalSize
// and use only the second code snippet below.
for (string line; getline(cin, line); )
{
    allLines.push_back(line);
    totalSize += line.size();
}

然后,您可以提前知道其大小来创建您的大string

string finalString;
finalString.reserve(totalSize);
for (vector<string>::iterator itS = allLines.begin(); itS != allLines.end(); ++itS)
{
    finalString += *itS;
}

不过,我应该提一下,只有在遇到性能问题时才应该这样做。不要尝试优化不需要的东西,否则会使程序复杂化而没有明显的好处。我们需要优化的地方通常是违反直觉的,并且可能因环境而异。因此,只有在您的分析工具告诉您需要时才这样做。

【讨论】:

  • 什么是你无法得到字节长度的文件?
  • 正如您在 OP 问题中看到的,他在这里使用cin 这是一个示例。此外,您可以访问您收到的网络资源,以此类推,以此类推。 (等等)
  • @Mic:你忘了看第一行吗? I'm reading in a text file of random ascii from an ifstream. 但是仍然有限制的正当理由。老实说,我认为 ddriver 相当短视。
  • @ddriver: /dev/random 是一个文件,但它的字节大小是多少?
  • 亮度在这里是正确的,我的停止点真的未知。我根据在读入期间从流中收集的信息停止(我有点“实时”解密这些字符的小组,长话短说!)。因此,简单地将存储空间 == 设置为文件大小是浪费的。我在这里阅读了一些其他可能有用的东西,感谢所有输入-
【解决方案2】:

如果您知道文件大小,请使用 result 的成员函数 'reserve()' 一次。

【讨论】:

  • 知道文件的大小不等于知道他有兴趣阅读的内容的长度。
  • @Sancho:正确。然而,后者才是有用的。如果文件为 600MB 并且您的消息长度为 1MB,则保留 整个 文件大小是相当愚蠢的。
  • @LightnessRacesinOrbit - “相当愚蠢”,“相当短视”......屈尊俯就? OP 似乎不知道他想做什么,而你想出令人惊叹的场景来描绘某种优势……不酷;)
  • @ddriver: *耸耸肩* 我只是说我所看到的。保留 600 倍的必要内存“相当愚蠢”,并且您 在假设 OP 能做什么和不能做什么方面“相当短视”。我设法很好地解析了需求,my interpretation has been confirmed.
  • @LightnessRacesinOrbit - 是的,是的,我们明白了,你很棒 ;) 太糟糕了,他也没有确认 15 GB 的输入文件,那将是一个真正的爆炸
【解决方案3】:

我太困了,无法为您整理任何可靠的数据,但最终,在不提前知道大小的情况下,您总是必须做一些事情 像这样。事实上,您的标准库实现足够智能,可以相当巧妙地处理字符串大小调整。 (尽管std::string 没有指数增长保证,std::vector 的方式却是这样。)

因此,尽管您可能会在前 50 次左右的迭代中看到不需要的重新分配,但一段时间后,重新分配的块变得如此之大,以至于重新分配变得很少。

如果您分析并发现这仍然是一个瓶颈,也许使用std::string::reserve 自己一个典型 数量。

【讨论】:

  • 与其冒险在每个连接上进行潜在的重新分配,不如收集链表中的子字符串并在最后将它们全部连接起来怎么样?尽管使用该解决方案,您在每个子字符串上都调用 new,因此总体上它可能不会比 std::string 的不太频繁但成本更高的 重新分配 更有效。
  • 引用的局部性意味着列表几乎没有用处;绳索可能有意义,但我严重怀疑性能问题是由于内存分配造成的......
  • @AlexChamberlain:好吧,后续的副本不会有帮助
  • 关于复杂性 - cppreference says 字符串构造函数采用范围具有线性复杂性,但我没有看到标准的确认(尽管它呈现的向量)。有意思,是cppreference的bug吗?
  • @LightnessRacesinOrbit,好吧,我看到了这条线 :) 这不是它看起来的样子 - X(t, m)m 是分配器,而不是迭代器。
【解决方案4】:

您正在为文件中的每一行复制结果数组(当您展开结果时)。而是预先分配结果并以指数方式增长:

std::string result;
result.reserve(1024); // pre-allocate a typical size

for (std::string line; std::getline(std::cin, line); )
{
    // every time we run out of space, double the available space
    while(result.capacity() < result.length() + line.length())
        result.reserve(result.capacity() * 2);

    result += line;
}

【讨论】:

    猜你喜欢
    • 2012-02-05
    • 2013-08-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-03-02
    相关资源
    最近更新 更多