【问题标题】:C++ faster way to do string addition?C++更快的方法来做字符串加法?
【发布时间】:2012-12-13 01:04:58
【问题描述】:

我发现标准字符串添加非常慢,所以我正在寻找一些可以加快我拥有的一些代码的提示/技巧。

我的代码基本结构如下:

inline void add_to_string(string data, string &added_data) {
   if(added_data.length()<1) added_data = added_data + "{";
   added_data = added_data+data;
}

int main()
{
   int some_int = 100;
   float some_float = 100.0;
   string some_string = "test";

   string added_data;
   added_data.reserve(1000*64);

   for(int ii=0;ii<1000;ii++)
   {
      //variables manipulated here
      some_int = ii;  
      some_float += ii;
      some_string.assign(ii%20,'A');
      //then we concatenate the strings!
      stringstream fragment;
      fragment<<some_int <<","<<some_float<<","<<some_string;
      add_to_string(fragment.str(),added_data);
   }
   return;
}

做一些基本的分析,我发现在 for 循环中使用了大量的时间。我可以做些什么来显着加快速度吗?使用 c 字符串代替 c++ 字符串会有所帮助吗?

【问题讨论】:

  • “做一些基本的分析,我发现在 for 循环中使用了很多时间。” 那么,这些时间还会用在什么地方呢? ,?这是整个程序!
  • @user788171 我不这么认为,std::string 上的常规加法运算符显式创建了一个新对象。与参数相同,特别是因为它不是 const。
  • @user788171:我认为最好是样本能反映这一点。
  • @user788171:已经讨论了一千次了。 sprintf 速度更快,但如果您输入错误,sstream 不会擦除您的硬盘。

标签: c++ string performance


【解决方案1】:

字符串添加不是您面临的问题。 std::stringstream 由于它的设计而被称为慢。在 for 循环的每次迭代中,stringstream 至少负责 2 次分配和 2 次删除。这 4 种操作中的每一种操作的成本都可能比字符串添加的成本更高。

分析以下内容并衡量差异:

std::string stringBuffer;
for(int ii=0;ii<1000;ii++)
{
  //variables manipulated here
  some_int = ii;  
  some_float += ii;
  some_string.assign(ii%20,'A');
  //then we concatenate the strings!
  char buffer[128];
  sprintf(buffer, "%i,%f,%s",some_int,some_float,some_string.c_str());
  stringBuffer = buffer;
  add_to_string(stringBuffer ,added_data);
}

理想情况下,将 sprintf 替换为 _snprintf 或您的编译器支持的等效项。

根据经验,默认情况下使用 stringstream 进行格式化,并在性能重要时切换到更快、更不安全的函数,如 sprintf、itoa 等。

编辑:那个,以及 didierc 所说的:added_data += data;

【讨论】:

    【解决方案2】:

    如果你不在循环中调用add_to_string,你可以节省大量的字符串操作。

    我相信这也是一样的(虽然我不是 C++ 专家,也不知道 stringstream 的确切作用):

    stringstream fragment;
    for(int ii=0;ii<1000;ii++)
    {
      //variables manipulated here
      some_int = ii;  
      some_float += ii;
      some_string.assign(ii%20,'A');
      //then we concatenate the strings!
       fragment<<some_int<<","<<some_float<<","<<some_string;
    }
    
    // inlined add_to_string call without the if-statement ;)
    added_data = "{" + fragment.str();
    

    【讨论】:

    • 所以这里的想法是我可能会提高速度,因为我可以使用 reserve() 为字符串分配内存,但不能对 stringstream 做类似的事情。
    • @Veger:stringstream 构建一个字符串。具体来说,该行是构建字符串"100,100.0,test" 一千次的非常慢的方法。
    • 它至少可以为您节省 999 次 stringstream.str() 调用、1000 次字符串连接、999 次 if 语句检查。对我来说似乎有所改进!但是,要确定您需要对此进行分析!
    • @MooingDuck 它直接构建字符串,或者当您调用str() 函数时?如果它直接构建它,你是对的,我会删除我的答案! :)
    • @Veger:其实它直接构建一个字符串,str返回一个copy。然而,由于他说some_int 等会在循环中发生变化,我实际上认为你的答案是最佳。请不要删除!
    【解决方案3】:

    我看到您在added_data 上使用了reserve 方法,这有助于避免在字符串增长时对其进行多次重新分配。

    您还应该尽可能使用+= 字符串运算符:

    added_data += data;
    

    我认为通过避免在进行连接时在临时字符串中来回复制added_data 来节省大量时间。

    这个+= 操作符是string::append 方法的一个更简单的版本,它只是将data 直接复制到added_data 的末尾。由于您进行了预留,因此仅该操作就应该非常快(几乎相当于 strcpy)。

    但是当你已经在使用字符串流来处理输入时,为什么还要经历这一切呢?一开始就把它全放在那里!

    stringstream 类确实效率不高。

    如有必要,您可以查看stringstream class 以获取有关如何使用它的更多信息,但您使用字符串作为缓冲区的解决方案似乎避免了该类速度问题。

    无论如何,除非您真的知道自己在做什么,否则不要尝试在纯 C 中重新实现速度关键代码。其他一些 SO 帖子支持这样做的想法,但我认为最好(阅读更安全)尽可能多地依赖标准库,它会随着时间的推移得到增强,并照顾许多你(或我)不会想到的极端案例。如果您的输入数据格式是一成不变的,那么您可能会开始考虑走这条路,否则就是过早的优化。

    【讨论】:

      【解决方案4】:

      如果您以"{" 开头added_data,您将能够从您的add_to_string 方法中删除if:当字符串为空时,if 只执行一次,因此您可以以及立即使其非空。

      此外,您的add_to_string 会复制data;这不是必需的,因为它不会被修改。接受const 引用的data 应该会加快您的速度。

      最后,将 added_datastring 更改为 sstream 应该可以让您在循环中附加到它,而无需在循环的每次迭代中创建、复制和丢弃 sstream 中介。

      【讨论】:

      • 你能澄清你在最后一段中的意思吗,added_data 应该是 stringstream 而不是 string?会不会对 added_data 进行大量调整会减慢速度?
      • @user788171:它会执行与 added_data 完全相同的调整大小,但它会更有效地执行此操作。
      • @user788171 肯定不会变慢:您始终可以预先分配您的sstream (here's a link showing how it can be done)。
      • 好的,如果预分配 sstream 技巧有效,那么这肯定会更快
      【解决方案5】:

      请查看 LLVM 中使用的 Twine

      Twine 是一种绳索,它使用 二叉树,其中字符串是节点的前序。由于 当使用它的结果时,Twine 可以有效地渲染到缓冲区中, 它避免了为中间字符串生成临时值的成本 结果 - 特别是在 Twine 结果永远不会的情况下 必需的。通过显式跟踪叶节点的类型,我们还可以避免 为转换操作创建临时字符串(例如 将整数附加到字符串)。

      它可能有助于解决您的问题。

      【讨论】:

        【解决方案6】:

        这种方法怎么样?

        这是 MSVC 2010 报告的 DevPartner。

        【讨论】:

          【解决方案7】:

          string newstring = stringA & stringB;

          我不认为字符串很慢,它的转换可以使它变慢 也许你的编译器可能会检查变量类型是否不匹配。

          【讨论】:

          • 我不知道你在说什么。
          • 我认为 user613326 不知道他在说什么。
          • 它的转换使它变慢,字符串并不慢
          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2020-11-10
          • 1970-01-01
          • 2013-09-28
          • 1970-01-01
          • 1970-01-01
          • 2011-03-20
          • 1970-01-01
          相关资源
          最近更新 更多